Skip to main content

fourcc/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub use fourcc_rs_macros::fourcc;
4use std::{char, fmt, primitive};
5
6#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
7#[cfg_attr(feature = "binrw", derive(binrw::BinRead, binrw::BinWrite))]
8#[cfg_attr(feature = "binrw", br(big))]
9/// A four-character code
10///
11/// If the `serde` feature is activated, it can be (de-) serialized using serde.
12///
13/// To read them from binary [`std::io::Read`]ers activate the `binrw` feature.
14pub struct FourCC(#[doc(hidden)] pub u32);
15
16impl FourCC {
17    /// Creates a new code with the given underlying value
18    pub const fn new(value: u32) -> Self {
19        Self(value)
20    }
21
22    /// Returns each byte of the code
23    fn components(&self) -> [u8; 4] {
24        [
25            ((self.0 >> 24) & 0xff) as u8,
26            ((self.0 >> 16) & 0xff) as u8,
27            ((self.0 >> 8) & 0xff) as u8,
28            (self.0 & 0xff) as u8,
29        ]
30    }
31
32    pub fn value(&self) -> u32 {
33        self.0
34    }
35
36    /// Returns each byte of the code as a character
37    ///
38    /// If a byte somehow can not be represented using [`primitive::char`] a REPLACEMENT CHARACTER
39    /// (0xFFFD) is substituted instead
40    fn chars(&self) -> [primitive::char; 4] {
41        self.components()
42            .map(|f| char::from_u32(f as u32).unwrap_or(char::from_u32(0xFFFD).unwrap()))
43    }
44
45    /// Returns the name of the code as a string
46    ///
47    /// This is a lossy conversion as unrepresentable values are replaced
48    pub fn name(&self) -> String {
49        self.chars().into_iter().collect::<String>()
50    }
51}
52
53#[cfg(feature = "serde")]
54impl serde::Serialize for FourCC {
55    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
56    where
57        S: serde::Serializer,
58    {
59        serializer.collect_str(&self.name())
60    }
61}
62
63#[cfg(feature = "serde")]
64impl<'de> serde::Deserialize<'de> for FourCC {
65    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
66    where
67        D: serde::Deserializer<'de>,
68    {
69        struct FourCCVisitor {}
70        impl<'de> serde::de::Visitor<'de> for FourCCVisitor {
71            type Value = FourCC;
72
73            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
74            where
75                E: serde::de::Error,
76            {
77                Err(serde::de::Error::invalid_type(
78                    serde::de::Unexpected::Bool(v),
79                    &self,
80                ))
81            }
82
83            fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
84            where
85                E: serde::de::Error,
86            {
87                self.visit_i64(v as i64)
88            }
89
90            fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E>
91            where
92                E: serde::de::Error,
93            {
94                self.visit_i64(v as i64)
95            }
96
97            fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
98            where
99                E: serde::de::Error,
100            {
101                self.visit_i64(v as i64)
102            }
103
104            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
105            where
106                E: serde::de::Error,
107            {
108                Err(serde::de::Error::invalid_type(
109                    serde::de::Unexpected::Signed(v),
110                    &self,
111                ))
112            }
113
114            fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
115            where
116                E: serde::de::Error,
117            {
118                let mut writer = String::new();
119                fmt::Write::write_fmt(&mut writer, format_args!("integer `{v}` as i128")).unwrap();
120                Err(serde::de::Error::invalid_type(
121                    serde::de::Unexpected::Other(writer.as_str()),
122                    &self,
123                ))
124            }
125
126            fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
127            where
128                E: serde::de::Error,
129            {
130                self.visit_u64(v as u64)
131            }
132
133            fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
134            where
135                E: serde::de::Error,
136            {
137                self.visit_u64(v as u64)
138            }
139
140            fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
141            where
142                E: serde::de::Error,
143            {
144                self.visit_u64(v as u64)
145            }
146
147            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
148            where
149                E: serde::de::Error,
150            {
151                Err(serde::de::Error::invalid_type(
152                    serde::de::Unexpected::Unsigned(v),
153                    &self,
154                ))
155            }
156
157            fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
158            where
159                E: serde::de::Error,
160            {
161                let mut writer = String::new();
162                fmt::Write::write_fmt(&mut writer, format_args!("integer `{v}` as u128")).unwrap();
163                Err(serde::de::Error::invalid_type(
164                    serde::de::Unexpected::Other(writer.as_str()),
165                    &self,
166                ))
167            }
168
169            fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
170            where
171                E: serde::de::Error,
172            {
173                self.visit_f64(v as f64)
174            }
175
176            fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
177            where
178                E: serde::de::Error,
179            {
180                Err(serde::de::Error::invalid_type(
181                    serde::de::Unexpected::Float(v),
182                    &self,
183                ))
184            }
185
186            fn visit_char<E>(self, v: char) -> Result<Self::Value, E>
187            where
188                E: serde::de::Error,
189            {
190                self.visit_str(v.encode_utf8(&mut [0u8; 4]))
191            }
192
193            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
194            where
195                E: serde::de::Error,
196            {
197                FourCC::try_from(v).map_err(|_| {
198                    serde::de::Error::invalid_type(serde::de::Unexpected::Str(v), &self)
199                })
200            }
201
202            fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
203            where
204                E: serde::de::Error,
205            {
206                self.visit_str(v)
207            }
208
209            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
210            where
211                E: serde::de::Error,
212            {
213                self.visit_str(&v)
214            }
215
216            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
217            where
218                E: serde::de::Error,
219            {
220                Err(serde::de::Error::invalid_type(
221                    serde::de::Unexpected::Bytes(v),
222                    &self,
223                ))
224            }
225
226            fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
227            where
228                E: serde::de::Error,
229            {
230                self.visit_bytes(v)
231            }
232
233            fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
234            where
235                E: serde::de::Error,
236            {
237                self.visit_bytes(&v)
238            }
239
240            fn visit_none<E>(self) -> Result<Self::Value, E>
241            where
242                E: serde::de::Error,
243            {
244                Err(serde::de::Error::invalid_type(
245                    serde::de::Unexpected::Option,
246                    &self,
247                ))
248            }
249
250            fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
251            where
252                D: serde::Deserializer<'de>,
253            {
254                let _ = deserializer;
255                Err(serde::de::Error::invalid_type(
256                    serde::de::Unexpected::Option,
257                    &self,
258                ))
259            }
260
261            fn visit_unit<E>(self) -> Result<Self::Value, E>
262            where
263                E: serde::de::Error,
264            {
265                Err(serde::de::Error::invalid_type(
266                    serde::de::Unexpected::Unit,
267                    &self,
268                ))
269            }
270
271            fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
272            where
273                D: serde::Deserializer<'de>,
274            {
275                let _ = deserializer;
276                Err(serde::de::Error::invalid_type(
277                    serde::de::Unexpected::NewtypeStruct,
278                    &self,
279                ))
280            }
281
282            fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
283            where
284                A: serde::de::SeqAccess<'de>,
285            {
286                let _ = seq;
287                Err(serde::de::Error::invalid_type(
288                    serde::de::Unexpected::Seq,
289                    &self,
290                ))
291            }
292
293            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
294            where
295                A: serde::de::MapAccess<'de>,
296            {
297                let _ = map;
298                Err(serde::de::Error::invalid_type(
299                    serde::de::Unexpected::Map,
300                    &self,
301                ))
302            }
303
304            fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
305            where
306                A: serde::de::EnumAccess<'de>,
307            {
308                let _ = data;
309                Err(serde::de::Error::invalid_type(
310                    serde::de::Unexpected::Enum,
311                    &self,
312                ))
313            }
314
315            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
316                f.write_str("string of four ASCII characters")
317            }
318        }
319        deserializer.deserialize_str(FourCCVisitor {})
320    }
321}
322
323impl fmt::Display for FourCC {
324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325        let [c1, c2, c3, c4] = self.chars();
326        format!("'{c1}{c2}{c3}{c4}'").fmt(f)
327    }
328}
329
330impl fmt::Debug for FourCC {
331    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332        let [c1, c2, c3, c4] = self.chars();
333        format!("'{c1}{c2}{c3}{c4}'").fmt(f)
334    }
335}
336
337impl From<FourCC> for u32 {
338    fn from(val: FourCC) -> Self {
339        val.0
340    }
341}
342
343impl From<u32> for FourCC {
344    fn from(value: u32) -> Self {
345        Self(value)
346    }
347}
348
349#[derive(Debug)]
350/// Error emitted when trying to create [`FourCC`]s from invalid input
351pub enum Error {
352    /// The given input is either too long or too short to be converted into a [`FourCC`]
353    InvalidInputLength,
354    /// The input contains characters that can not be represented in a four-character code
355    InvalidInputChar,
356}
357
358impl TryFrom<String> for FourCC {
359    type Error = Error;
360
361    fn try_from(value: String) -> Result<Self, Self::Error> {
362        if value.len() != 4 {
363            return Err(Error::InvalidInputLength);
364        }
365
366        if !value.is_ascii() {
367            return Err(Error::InvalidInputChar);
368        };
369
370        Ok(Self(value.chars().enumerate().fold(
371            0u32,
372            |acc: u32, (idx, value): (usize, char)| -> u32 {
373                let mut buf = vec![0u8];
374                value.encode_utf8(&mut buf);
375
376                acc | ((buf[0] as u32) << (24 - 8 * idx))
377            },
378        )))
379    }
380}
381
382impl TryFrom<&str> for FourCC {
383    type Error = Error;
384
385    fn try_from(value: &str) -> Result<Self, Self::Error> {
386        if value.len() != 4 {
387            return Err(Error::InvalidInputLength);
388        }
389
390        if !value.is_ascii() {
391            return Err(Error::InvalidInputChar);
392        };
393
394        fn to_u8(c: char) -> u8 {
395            let mut buf = vec![0u8];
396            c.encode_utf8(&mut buf);
397            buf[0]
398        }
399
400        let chars: Vec<_> = value.chars().collect();
401
402        Ok(Self(u32_from_u8s(
403            to_u8(chars[0]),
404            to_u8(chars[1]),
405            to_u8(chars[2]),
406            to_u8(chars[3]),
407        )))
408    }
409}
410
411impl From<[u8; 4]> for FourCC {
412    fn from(value: [u8; 4]) -> Self {
413        Self(u32_from_u8s(value[0], value[1], value[2], value[3]))
414    }
415}
416
417impl From<&[u8; 4]> for FourCC {
418    fn from(value: &[u8; 4]) -> Self {
419        Self(u32_from_u8s(value[0], value[1], value[2], value[3]))
420    }
421}
422
423#[inline]
424const fn u32_from_u8s(v1: u8, v2: u8, v3: u8, v4: u8) -> u32 {
425    ((v1 as u32) << 24) | ((v2 as u32) << 16) | ((v3 as u32) << 8) | (v4 as u32)
426}
427
428#[cfg(test)]
429mod test {
430    use crate::{Error, FourCC};
431
432    #[test]
433    fn creation() {
434        assert_eq!(FourCC::new(0), FourCC(0));
435        assert_eq!(FourCC::new(0xabcdef01), FourCC(0xabcdef01));
436    }
437
438    #[test]
439    fn display() {
440        assert_eq!(&format!("{}", FourCC::new(0x41424344)), "'ABCD'");
441    }
442
443    #[test]
444    fn display_options() {
445        assert_eq!(&format!("{:8}", FourCC::new(0x41424344)), "'ABCD'  ");
446        assert_eq!(&format!("{:8}", FourCC::new(0x00000000)), "'\0\0\0\0'  ");
447        assert_eq!(&format!("{:8}", FourCC::new(0x00010000)), "'\0\u{1}\0\0'  ");
448    }
449
450    #[test]
451    fn debug() {
452        assert_eq!(&format!("{:?}", FourCC::new(0x41424344)), "\"'ABCD'\"");
453    }
454
455    #[test]
456    fn components() {
457        let code = FourCC::new(0xabcdef01);
458        assert_eq!(code.components(), [0xab, 0xcd, 0xef, 0x01]);
459    }
460
461    #[test]
462    fn chars() {
463        let code: FourCC = b"ABCD".into();
464        assert_eq!(code.chars(), ['A', 'B', 'C', 'D']);
465    }
466
467    #[test]
468    fn name() {
469        let code: FourCC = b"ABCD".into();
470        assert_eq!(code.name(), "ABCD".to_string());
471    }
472
473    #[test]
474    fn from_u32() {
475        let code: FourCC = FourCC::from(0xabcdef01);
476        assert_eq!(code, FourCC(0xabcdef01));
477    }
478
479    #[test]
480    fn into_u32() {
481        let code: FourCC = FourCC::new(0xabcdef01);
482        let code: u32 = code.into();
483        assert_eq!(code, 0xabcdef01u32);
484    }
485
486    #[test]
487    fn try_from_string() {
488        let code: FourCC = String::from("ABCD").try_into().unwrap();
489        assert_eq!(code, FourCC::new(0x41424344));
490
491        assert!(matches!(
492            String::from("ABCDE").try_into() as Result<FourCC, _>,
493            Err(Error::InvalidInputLength)
494        ));
495
496        assert!(matches!(
497            String::from("ABC").try_into() as Result<FourCC, _>,
498            Err(Error::InvalidInputLength)
499        ));
500
501        assert!(matches!(
502            String::from("AB©").try_into() as Result<FourCC, _>,
503            Err(Error::InvalidInputChar)
504        ));
505    }
506
507    #[test]
508    fn try_from_str() {
509        let code: FourCC = "ABCD".try_into().unwrap();
510        assert_eq!(code, FourCC::new(0x41424344));
511
512        assert!(matches!(
513            "ABCDE".try_into() as Result<FourCC, _>,
514            Err(Error::InvalidInputLength)
515        ));
516
517        assert!(matches!(
518            "ABC".try_into() as Result<FourCC, _>,
519            Err(Error::InvalidInputLength)
520        ));
521
522        assert!(matches!(
523            "AB©".try_into() as Result<FourCC, _>,
524            Err(Error::InvalidInputChar)
525        ));
526    }
527
528    #[test]
529    fn from_u8_array() {
530        let code: FourCC = b"ABCD".to_owned().into();
531        assert_eq!(code, FourCC::new(0x41424344));
532    }
533
534    #[test]
535    fn from_u8_array_ref() {
536        let code: FourCC = b"ABCD".into();
537        assert_eq!(code, FourCC::new(0x41424344));
538    }
539}
540
541#[cfg(all(test, feature = "binrw"))]
542mod feature_binrw {
543    use binrw::BinReaderExt as _;
544    use std::io;
545
546    use crate::FourCC;
547
548    #[test]
549    fn read() {
550        let mut reader = io::Cursor::new(b"\x41\x42\x43\x44");
551        let fourcc: FourCC = reader.read_be().unwrap();
552        let expected: FourCC = "ABCD".try_into().unwrap();
553
554        assert_eq!(fourcc, expected);
555    }
556}
557
558#[cfg(all(test, feature = "serde"))]
559mod feature_serde {
560    use crate::FourCC;
561
562    #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
563    struct MySample {
564        a_code: FourCC,
565    }
566
567    #[test]
568    fn read() {
569        let sample = MySample {
570            a_code: FourCC::new(0x41424344),
571        };
572        let json = serde_json::to_string(&sample).unwrap();
573        assert_eq!(&json, "{\"a_code\":\"ABCD\"}");
574
575        let restored_sample: MySample = serde_json::from_str(&json).unwrap();
576        assert_eq!(sample, restored_sample);
577    }
578}