mmd_anim_format/
format.rs1use serde::Serialize;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4#[serde(rename_all = "camelCase")]
5pub enum MmdFormatKind {
6 Pmd,
7 Pmx,
8 Vmd,
9 Vpd,
10 Pmm,
11 Nmd,
12 X,
13 Vac,
14 Unknown,
15}
16
17pub fn detect_mmd_format(data: &[u8], file_name: Option<&str>) -> MmdFormatKind {
18 if data.starts_with(b"PMX ") {
19 return MmdFormatKind::Pmx;
20 }
21 if data.starts_with(b"Pmd") {
22 return MmdFormatKind::Pmd;
23 }
24 if data.starts_with(b"Vocaloid Motion Data") {
25 return MmdFormatKind::Vmd;
26 }
27 if data.starts_with(b"Vocaloid Pose Data") {
28 return MmdFormatKind::Vpd;
29 }
30 if data.starts_with(b"Polygon Movie maker ") {
31 return MmdFormatKind::Pmm;
32 }
33 if data.starts_with(b"xof ") {
34 return MmdFormatKind::X;
35 }
36 if looks_like_nmd(data, file_name) {
37 return MmdFormatKind::Nmd;
38 }
39 match extension(file_name).as_deref() {
40 Some("x") => MmdFormatKind::X,
41 Some("vac") => MmdFormatKind::Vac,
42 Some("nmd") => MmdFormatKind::Nmd,
43 Some("pmd") => MmdFormatKind::Pmd,
44 Some("pmx") => MmdFormatKind::Pmx,
45 Some("vmd") => MmdFormatKind::Vmd,
46 Some("vpd") => MmdFormatKind::Vpd,
47 Some("pmm") => MmdFormatKind::Pmm,
48 _ => MmdFormatKind::Unknown,
49 }
50}
51
52fn extension(file_name: Option<&str>) -> Option<String> {
53 file_name?
54 .rsplit_once('.')
55 .map(|(_, ext)| ext.trim().to_ascii_lowercase())
56}
57
58fn looks_like_nmd(data: &[u8], file_name: Option<&str>) -> bool {
59 matches!(extension(file_name).as_deref(), Some("nmd"))
60 || data.starts_with(b"NMD")
61 || data.starts_with(b"Nanoem Motion Data")
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn detects_core_mmd_formats_from_magic_bytes() {
70 assert_eq!(detect_mmd_format(b"PMX test", None), MmdFormatKind::Pmx);
71 assert_eq!(detect_mmd_format(b"Pmd\x00", None), MmdFormatKind::Pmd);
72 assert_eq!(
73 detect_mmd_format(b"Vocaloid Motion Data 0002", None),
74 MmdFormatKind::Vmd
75 );
76 assert_eq!(
77 detect_mmd_format(b"Vocaloid Pose Data file", None),
78 MmdFormatKind::Vpd
79 );
80 assert_eq!(
81 detect_mmd_format(b"Polygon Movie maker 0002", None),
82 MmdFormatKind::Pmm
83 );
84 assert_eq!(
85 detect_mmd_format(b"xof 0303txt 0032", None),
86 MmdFormatKind::X
87 );
88 assert_eq!(
89 detect_mmd_format(b"Nanoem Motion Data", None),
90 MmdFormatKind::Nmd
91 );
92 }
93
94 #[test]
95 fn falls_back_to_case_insensitive_extension() {
96 assert_eq!(
97 detect_mmd_format(b"", Some("motion.NMD")),
98 MmdFormatKind::Nmd
99 );
100 assert_eq!(
101 detect_mmd_format(b"", Some("accessory.VAC")),
102 MmdFormatKind::Vac
103 );
104 assert_eq!(
105 detect_mmd_format(b"", Some("model.PMD")),
106 MmdFormatKind::Pmd
107 );
108 assert_eq!(
109 detect_mmd_format(b"", Some("unknown.bin")),
110 MmdFormatKind::Unknown
111 );
112 }
113}