#![doc = include_str!("../README.md")]
pub use fourcc_rs_macros::fourcc;
#[doc(hidden)]
pub use fourcc_rs_macros::fourcc_rexport;
use std::{char, fmt, primitive};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "binrw", derive(binrw::BinRead, binrw::BinWrite))]
#[cfg_attr(feature = "binrw", br(big))]
pub struct FourCC(#[doc(hidden)] pub u32);
impl FourCC {
pub const fn new(value: u32) -> Self {
Self(value)
}
fn components(&self) -> [u8; 4] {
[
((self.0 >> 24) & 0xff) as u8,
((self.0 >> 16) & 0xff) as u8,
((self.0 >> 8) & 0xff) as u8,
(self.0 & 0xff) as u8,
]
}
pub fn value(&self) -> u32 {
self.0
}
fn chars(&self) -> [primitive::char; 4] {
self.components()
.map(|f| char::from_u32(f as u32).unwrap_or(char::from_u32(0xFFFD).unwrap()))
}
pub fn name(&self) -> String {
self.chars().into_iter().collect::<String>()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for FourCC {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(&self.name())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for FourCC {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct FourCCVisitor {}
impl<'de> serde::de::Visitor<'de> for FourCCVisitor {
type Value = FourCC;
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Bool(v),
&self,
))
}
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_i64(v as i64)
}
fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_i64(v as i64)
}
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_i64(v as i64)
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Signed(v),
&self,
))
}
fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let mut writer = String::new();
fmt::Write::write_fmt(&mut writer, format_args!("integer `{v}` as i128")).unwrap();
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Other(writer.as_str()),
&self,
))
}
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_u64(v as u64)
}
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_u64(v as u64)
}
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_u64(v as u64)
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Unsigned(v),
&self,
))
}
fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let mut writer = String::new();
fmt::Write::write_fmt(&mut writer, format_args!("integer `{v}` as u128")).unwrap();
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Other(writer.as_str()),
&self,
))
}
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_f64(v as f64)
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Float(v),
&self,
))
}
fn visit_char<E>(self, v: char) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v.encode_utf8(&mut [0u8; 4]))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
FourCC::try_from(v).map_err(|_| {
serde::de::Error::invalid_type(serde::de::Unexpected::Str(v), &self)
})
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v)
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&v)
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Bytes(v),
&self,
))
}
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(v)
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(&v)
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Option,
&self,
))
}
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
let _ = deserializer;
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Option,
&self,
))
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Unit,
&self,
))
}
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
let _ = deserializer;
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::NewtypeStruct,
&self,
))
}
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let _ = seq;
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Seq,
&self,
))
}
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let _ = map;
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Map,
&self,
))
}
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
where
A: serde::de::EnumAccess<'de>,
{
let _ = data;
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Enum,
&self,
))
}
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("string of four ASCII characters")
}
}
deserializer.deserialize_str(FourCCVisitor {})
}
}
impl fmt::Display for FourCC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let [c1, c2, c3, c4] = self.chars();
format!("'{c1}{c2}{c3}{c4}'").fmt(f)
}
}
impl fmt::Debug for FourCC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let [c1, c2, c3, c4] = self.chars();
format!("'{c1}{c2}{c3}{c4}'").fmt(f)
}
}
impl From<FourCC> for u32 {
fn from(val: FourCC) -> Self {
val.0
}
}
impl From<u32> for FourCC {
fn from(value: u32) -> Self {
Self(value)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
InvalidInputLength,
InvalidInputChar,
}
impl TryFrom<String> for FourCC {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
if value.len() != 4 {
return Err(Error::InvalidInputLength);
}
if !value.is_ascii() {
return Err(Error::InvalidInputChar);
};
Ok(Self(value.chars().enumerate().fold(
0u32,
|acc: u32, (idx, value): (usize, char)| -> u32 {
let mut buf = vec![0u8];
value.encode_utf8(&mut buf);
acc | ((buf[0] as u32) << (24 - 8 * idx))
},
)))
}
}
impl TryFrom<&str> for FourCC {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
if value.len() != 4 {
return Err(Error::InvalidInputLength);
}
if !value.is_ascii() {
return Err(Error::InvalidInputChar);
};
fn to_u8(c: char) -> u8 {
let mut buf = vec![0u8];
c.encode_utf8(&mut buf);
buf[0]
}
let chars: Vec<_> = value.chars().collect();
Ok(Self(u32_from_u8s(
to_u8(chars[0]),
to_u8(chars[1]),
to_u8(chars[2]),
to_u8(chars[3]),
)))
}
}
impl From<[u8; 4]> for FourCC {
fn from(value: [u8; 4]) -> Self {
Self(u32_from_u8s(value[0], value[1], value[2], value[3]))
}
}
impl From<&[u8; 4]> for FourCC {
fn from(value: &[u8; 4]) -> Self {
Self(u32_from_u8s(value[0], value[1], value[2], value[3]))
}
}
#[inline]
const fn u32_from_u8s(v1: u8, v2: u8, v3: u8, v4: u8) -> u32 {
((v1 as u32) << 24) | ((v2 as u32) << 16) | ((v3 as u32) << 8) | (v4 as u32)
}
#[cfg(test)]
mod test {
use crate::{Error, FourCC};
#[test]
fn creation() {
assert_eq!(FourCC::new(0), FourCC(0));
assert_eq!(FourCC::new(0xabcdef01), FourCC(0xabcdef01));
}
#[test]
fn display() {
assert_eq!(&format!("{}", FourCC::new(0x41424344)), "'ABCD'");
}
#[test]
fn display_options() {
assert_eq!(&format!("{:8}", FourCC::new(0x41424344)), "'ABCD' ");
assert_eq!(&format!("{:8}", FourCC::new(0x00000000)), "'\0\0\0\0' ");
assert_eq!(&format!("{:8}", FourCC::new(0x00010000)), "'\0\u{1}\0\0' ");
}
#[test]
fn debug() {
assert_eq!(&format!("{:?}", FourCC::new(0x41424344)), "\"'ABCD'\"");
}
#[test]
fn components() {
let code = FourCC::new(0xabcdef01);
assert_eq!(code.components(), [0xab, 0xcd, 0xef, 0x01]);
}
#[test]
fn chars() {
let code: FourCC = b"ABCD".into();
assert_eq!(code.chars(), ['A', 'B', 'C', 'D']);
}
#[test]
fn name() {
let code: FourCC = b"ABCD".into();
assert_eq!(code.name(), "ABCD".to_string());
}
#[test]
fn from_u32() {
let code: FourCC = FourCC::from(0xabcdef01);
assert_eq!(code, FourCC(0xabcdef01));
}
#[test]
fn into_u32() {
let code: FourCC = FourCC::new(0xabcdef01);
let code: u32 = code.into();
assert_eq!(code, 0xabcdef01u32);
}
#[test]
fn try_from_string() {
let code: FourCC = String::from("ABCD").try_into().unwrap();
assert_eq!(code, FourCC::new(0x41424344));
assert!(matches!(
String::from("ABCDE").try_into() as Result<FourCC, _>,
Err(Error::InvalidInputLength)
));
assert!(matches!(
String::from("ABC").try_into() as Result<FourCC, _>,
Err(Error::InvalidInputLength)
));
assert!(matches!(
String::from("AB©").try_into() as Result<FourCC, _>,
Err(Error::InvalidInputChar)
));
}
#[test]
fn try_from_str() {
let code: FourCC = "ABCD".try_into().unwrap();
assert_eq!(code, FourCC::new(0x41424344));
assert!(matches!(
"ABCDE".try_into() as Result<FourCC, _>,
Err(Error::InvalidInputLength)
));
assert!(matches!(
"ABC".try_into() as Result<FourCC, _>,
Err(Error::InvalidInputLength)
));
assert!(matches!(
"AB©".try_into() as Result<FourCC, _>,
Err(Error::InvalidInputChar)
));
}
#[test]
fn from_u8_array() {
let code: FourCC = b"ABCD".to_owned().into();
assert_eq!(code, FourCC::new(0x41424344));
}
#[test]
fn from_u8_array_ref() {
let code: FourCC = b"ABCD".into();
assert_eq!(code, FourCC::new(0x41424344));
}
}
#[cfg(all(test, feature = "binrw"))]
mod feature_binrw {
use binrw::BinReaderExt as _;
use std::io;
use crate::FourCC;
#[test]
fn read() {
let mut reader = io::Cursor::new(b"\x41\x42\x43\x44");
let fourcc: FourCC = reader.read_be().unwrap();
let expected: FourCC = "ABCD".try_into().unwrap();
assert_eq!(fourcc, expected);
}
}
#[cfg(all(test, feature = "serde"))]
mod feature_serde {
use crate::FourCC;
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
struct MySample {
a_code: FourCC,
}
#[test]
fn read() {
let sample = MySample {
a_code: FourCC::new(0x41424344),
};
let json = serde_json::to_string(&sample).unwrap();
assert_eq!(&json, "{\"a_code\":\"ABCD\"}");
let restored_sample: MySample = serde_json::from_str(&json).unwrap();
assert_eq!(sample, restored_sample);
}
}