1use bon::Builder;
2use std::{fmt, time::Duration};
3
4use crate::{
5 atom::{
6 util::{mp4_timestamp_now, scaled_duration, unscaled_duration},
7 FourCC,
8 },
9 parser::ParseAtomData,
10 writer::SerializeAtom,
11 ParseError,
12};
13
14pub const MDHD: FourCC = FourCC::new(b"mdhd");
15
16macro_rules! define_language_code_enum {
17 ($( #[$meta:meta] )* $name:ident { $( $( #[$tag:meta] )* $variant:ident => $chars:literal ),+ $(,)? }) => {
18 $(#[$meta])*
19 pub enum $name {
20 $( $( #[$tag] )* $variant ),+,
21 Other([u8; 3]),
22 }
23
24 impl $name {
25 fn from_chars(chars: &[u8; 3]) -> Self {
26 match chars {
27 $( $chars => Self::$variant ),+,
28 _ => Self::Other(*chars),
29 }
30 }
31
32 fn as_chars(&self) -> &[u8; 3] {
33 match self {
34 $( Self::$variant => $chars ),+,
35 Self::Other(chars) => chars,
36 }
37 }
38 }
39
40 impl fmt::Display for $name {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 let chars = match self {
43 $( Self::$variant => $chars ),+,
44 Self::Other(chars) => &[chars[0] as u8, chars[1] as u8, chars[2] as u8],
45 };
46 write!(f, "{}{}{}", chars[0] as char, chars[1] as char, chars[2] as char)
47 }
48 }
49 };
50}
51
52define_language_code_enum!(
53 #[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
55 #[non_exhaustive]
56 LanguageCode {
57 English => b"eng",
58 Spanish => b"spa",
59 French => b"fra",
60 German => b"deu",
61 Italian => b"ita",
62 Japanese => b"jpn",
63 Korean => b"kor",
64 Chinese => b"chi",
65 Russian => b"rus",
66 Arabic => b"ara",
67 Portuguese => b"por",
68 #[default]
69 Undetermined => b"und",
70 }
71);
72
73impl From<u16> for LanguageCode {
74 fn from(packed: u16) -> Self {
75 let char1 = (((packed >> 10) & 0x1F) + 0x60) as u8;
76 let char2 = (((packed >> 5) & 0x1F) + 0x60) as u8;
77 let char3 = ((packed & 0x1F) + 0x60) as u8;
78
79 let lang: [u8; 3] = [char1, char2, char3];
80
81 Self::from_chars(&lang)
82 }
83}
84
85impl From<LanguageCode> for u16 {
86 fn from(value: LanguageCode) -> Self {
87 let chars = value.as_chars();
88
89 let char1_bits = (chars[0] - 0x60) & 0x1F;
90 let char2_bits = (chars[1] - 0x60) & 0x1F;
91 let char3_bits = (chars[2] - 0x60) & 0x1F;
92
93 (u16::from(char1_bits) << 10) | (u16::from(char2_bits) << 5) | u16::from(char3_bits)
94 }
95}
96
97#[derive(Default, Debug, Clone, Builder)]
98pub struct MediaHeaderAtom {
99 #[builder(default = 0)]
101 pub version: u8,
102 #[builder(default = [0u8; 3])]
104 pub flags: [u8; 3],
105 #[builder(default = mp4_timestamp_now())]
107 pub creation_time: u64,
108 #[builder(default = mp4_timestamp_now())]
110 pub modification_time: u64,
111 pub timescale: u32,
113 pub duration: u64,
115 #[builder(default = LanguageCode::Undetermined)]
117 pub language: LanguageCode,
118 #[builder(default = 0)]
120 pub pre_defined: u16,
121}
122
123impl MediaHeaderAtom {
124 pub fn duration(&self) -> Duration {
125 unscaled_duration(self.duration, u64::from(self.timescale))
126 }
127
128 pub fn update_duration<F>(&mut self, mut closure: F) -> &mut Self
129 where
130 F: FnMut(Duration) -> Duration,
131 {
132 self.duration = scaled_duration(closure(self.duration()), u64::from(self.timescale));
133 self
134 }
135}
136
137impl ParseAtomData for MediaHeaderAtom {
138 fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
139 crate::atom::util::parser::assert_atom_type!(atom_type, MDHD);
140 use crate::atom::util::parser::stream;
141 use winnow::Parser;
142 Ok(parser::parse_mdhd_data.parse(stream(input))?)
143 }
144}
145
146impl SerializeAtom for MediaHeaderAtom {
147 fn atom_type(&self) -> FourCC {
148 MDHD
149 }
150
151 fn into_body_bytes(self) -> Vec<u8> {
152 serializer::serialize_mdhd_atom(self)
153 }
154}
155
156mod serializer {
157 use super::MediaHeaderAtom;
158
159 pub fn serialize_mdhd_atom(mdhd: MediaHeaderAtom) -> Vec<u8> {
160 let mut data = Vec::new();
161
162 let version: u8 = if mdhd.version == 1
163 || mdhd.creation_time > u64::from(u32::MAX)
164 || mdhd.modification_time > u64::from(u32::MAX)
165 || mdhd.duration > u64::from(u32::MAX)
166 {
167 1
168 } else {
169 0
170 };
171
172 let be_u32_or_u64 = |v: u64| match version {
173 0 => u32::try_from(v).unwrap().to_be_bytes().to_vec(),
174 1 => v.to_be_bytes().to_vec(),
175 _ => unreachable!(),
176 };
177
178 data.extend(version.to_be_bytes());
179 data.extend(mdhd.flags);
180 data.extend(be_u32_or_u64(mdhd.creation_time));
181 data.extend(be_u32_or_u64(mdhd.modification_time));
182 data.extend(mdhd.timescale.to_be_bytes());
183 data.extend(be_u32_or_u64(mdhd.duration));
184 data.extend(u16::from(mdhd.language).to_be_bytes());
185 data.extend(mdhd.pre_defined.to_be_bytes());
186
187 data
188 }
189}
190
191mod parser {
192 use winnow::{
193 binary::{be_u16, be_u32, be_u64},
194 combinator::{seq, trace},
195 error::{StrContext, StrContextValue},
196 ModalResult, Parser,
197 };
198
199 use super::{LanguageCode, MediaHeaderAtom};
200 use crate::atom::util::parser::{be_u32_as_u64, flags3, version, Stream};
201
202 pub fn parse_mdhd_data(input: &mut Stream<'_>) -> ModalResult<MediaHeaderAtom> {
203 let be_u32_or_u64 = |version: u8| {
204 let be_u64_type_fix =
205 |input: &mut Stream<'_>| -> ModalResult<u64> { be_u64.parse_next(input) };
206 match version {
207 0 => be_u32_as_u64,
208 1 => be_u64_type_fix,
209 _ => unreachable!(),
210 }
211 };
212
213 trace(
214 "mdhd",
215 seq!(MediaHeaderAtom {
216 version: version
217 .verify(|version| *version <= 1)
218 .context(StrContext::Expected(StrContextValue::Description(
219 "expected version 0 or 1"
220 ))),
221 flags: flags3,
222 creation_time: be_u32_or_u64(version),
223 modification_time: be_u32_or_u64(version),
224 timescale: be_u32,
225 duration: be_u32_or_u64(version),
226 language: be_u16.map(LanguageCode::from),
227 pre_defined: be_u16,
228 })
229 .context(StrContext::Label("mdhd")),
230 )
231 .parse_next(input)
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use crate::atom::test_utils::test_atom_roundtrip;
239
240 #[test]
242 fn test_mdhd_roundtrip() {
243 test_atom_roundtrip::<MediaHeaderAtom>(MDHD);
244 }
245}