use crate::blocks::IdBlock;
use crate::error::{Mf4Error, Result};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Mf4Version {
V4_0 {
raw: u16,
},
V4_1 {
raw: u16,
},
V4_2 {
raw: u16,
},
Unknown {
major: u16,
minor: u16,
raw: u16,
},
}
impl Mf4Version {
pub fn from_parts(major: u16, minor: u16) -> Self {
let raw = major * 100 + minor;
match (major, minor) {
(4, 0) => Mf4Version::V4_0 { raw },
(4, 1) => Mf4Version::V4_1 { raw },
(4, 10..=19) => Mf4Version::V4_1 { raw }, (4, 2) => Mf4Version::V4_2 { raw },
(4, 20..=29) => Mf4Version::V4_2 { raw }, _ => Mf4Version::Unknown { major, minor, raw },
}
}
pub fn from_version_number(version: u16) -> Self {
let major = version / 100;
let minor = version % 100;
Self::from_parts(major, minor)
}
pub fn from_id_block(id: &IdBlock) -> Self {
Self::from_version_number(id.version_number)
}
pub fn major(&self) -> u16 {
match self {
Mf4Version::V4_0 { .. } => 4,
Mf4Version::V4_1 { .. } => 4,
Mf4Version::V4_2 { .. } => 4,
Mf4Version::Unknown { major, .. } => *major,
}
}
pub fn minor(&self) -> u16 {
match self {
Mf4Version::V4_0 { raw } => raw % 100,
Mf4Version::V4_1 { raw } => raw % 100,
Mf4Version::V4_2 { raw } => raw % 100,
Mf4Version::Unknown { minor, .. } => *minor,
}
}
pub fn raw(&self) -> u16 {
match self {
Mf4Version::V4_0 { raw } => *raw,
Mf4Version::V4_1 { raw } => *raw,
Mf4Version::V4_2 { raw } => *raw,
Mf4Version::Unknown { raw, .. } => *raw,
}
}
pub fn is_supported(&self) -> bool {
matches!(
self,
Mf4Version::V4_0 { .. } | Mf4Version::V4_1 { .. } | Mf4Version::V4_2 { .. }
)
}
pub fn validate(&self) -> Result<()> {
if self.is_supported() {
Ok(())
} else {
Err(Mf4Error::UnsupportedVersion {
major: self.major(),
minor: self.minor(),
})
}
}
}
impl fmt::Display for Mf4Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{:02}", self.major(), self.minor())
}
}
impl Default for Mf4Version {
fn default() -> Self {
Mf4Version::V4_2 { raw: 420 }
}
}
pub trait VersionedParser {
fn version(&self) -> Mf4Version;
fn can_handle(&self, version: Mf4Version) -> bool {
self.version() == version
}
fn hd_block_offset(&self) -> u64 {
64
}
fn supports_sample_reduction(&self) -> bool {
matches!(
self.version(),
Mf4Version::V4_1 { .. } | Mf4Version::V4_2 { .. }
)
}
fn supports_events(&self) -> bool {
true }
fn supports_attachments(&self) -> bool {
true }
}
#[derive(Debug, Clone, Copy, Default)]
pub struct V40Parser;
impl VersionedParser for V40Parser {
fn version(&self) -> Mf4Version {
Mf4Version::V4_0 { raw: 400 }
}
fn supports_sample_reduction(&self) -> bool {
false
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct V41Parser;
impl VersionedParser for V41Parser {
fn version(&self) -> Mf4Version {
Mf4Version::V4_1 { raw: 410 }
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct V42Parser;
impl VersionedParser for V42Parser {
fn version(&self) -> Mf4Version {
Mf4Version::V4_2 { raw: 420 }
}
}
pub fn get_parser_for_version(version: Mf4Version) -> Box<dyn VersionedParser + Send + Sync> {
match version {
Mf4Version::V4_0 { .. } => Box::new(V40Parser),
Mf4Version::V4_1 { .. } => Box::new(V41Parser),
Mf4Version::V4_2 { .. } => Box::new(V42Parser),
Mf4Version::Unknown { .. } => {
Box::new(V42Parser)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_from_parts() {
assert!(matches!(
Mf4Version::from_parts(4, 0),
Mf4Version::V4_0 { raw: 400 }
));
assert!(matches!(
Mf4Version::from_parts(4, 1),
Mf4Version::V4_1 { raw: 401 }
));
assert!(matches!(
Mf4Version::from_parts(4, 2),
Mf4Version::V4_2 { raw: 402 }
));
assert!(matches!(
Mf4Version::from_parts(4, 10),
Mf4Version::V4_1 { raw: 410 }
));
assert!(matches!(
Mf4Version::from_parts(4, 11),
Mf4Version::V4_1 { raw: 411 }
));
assert!(matches!(
Mf4Version::from_parts(4, 20),
Mf4Version::V4_2 { raw: 420 }
));
assert!(matches!(
Mf4Version::from_parts(5, 0),
Mf4Version::Unknown {
major: 5,
minor: 0,
raw: 500
}
));
}
#[test]
fn test_version_from_version_number() {
assert!(matches!(
Mf4Version::from_version_number(400),
Mf4Version::V4_0 { .. }
));
assert!(matches!(
Mf4Version::from_version_number(410),
Mf4Version::V4_1 { .. }
));
assert!(matches!(
Mf4Version::from_version_number(411),
Mf4Version::V4_1 { raw: 411 }
));
assert!(matches!(
Mf4Version::from_version_number(420),
Mf4Version::V4_2 { .. }
));
}
#[test]
fn test_version_display() {
assert_eq!(format!("{}", Mf4Version::from_version_number(400)), "4.00");
assert_eq!(format!("{}", Mf4Version::from_version_number(410)), "4.10");
assert_eq!(format!("{}", Mf4Version::from_version_number(411)), "4.11");
assert_eq!(format!("{}", Mf4Version::from_version_number(420)), "4.20");
}
#[test]
fn test_version_is_supported() {
assert!(Mf4Version::V4_0 { raw: 400 }.is_supported());
assert!(Mf4Version::V4_1 { raw: 411 }.is_supported());
assert!(Mf4Version::V4_2 { raw: 420 }.is_supported());
assert!(!Mf4Version::Unknown {
major: 5,
minor: 0,
raw: 500
}
.is_supported());
}
#[test]
fn test_version_validate() {
assert!(Mf4Version::V4_2 { raw: 420 }.validate().is_ok());
assert!(Mf4Version::Unknown {
major: 5,
minor: 0,
raw: 500
}
.validate()
.is_err());
}
#[test]
fn test_versioned_parser() {
let parser = get_parser_for_version(Mf4Version::V4_0 { raw: 400 });
assert!(!parser.supports_sample_reduction());
let parser = get_parser_for_version(Mf4Version::V4_2 { raw: 420 });
assert!(parser.supports_sample_reduction());
}
}