Skip to main content

fourcc/
lib.rs

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