mp4_edit/atom/leaf/
mvhd.rs1use bon::Builder;
2
3use std::time::Duration;
4
5use crate::{
6 atom::{
7 util::{mp4_timestamp_now, scaled_duration, unscaled_duration},
8 FourCC,
9 },
10 parser::ParseAtomData,
11 writer::SerializeAtom,
12 ParseError,
13};
14
15pub const MVHD: FourCC = FourCC::new(b"mvhd");
16
17#[derive(Debug, Clone, Builder)]
18pub struct MovieHeaderAtom {
19 #[builder(default = 0)]
21 pub version: u8,
22 #[builder(default = [0u8; 3])]
24 pub flags: [u8; 3],
25 #[builder(default = mp4_timestamp_now())]
27 pub creation_time: u64,
28 #[builder(default = mp4_timestamp_now())]
30 pub modification_time: u64,
31 pub timescale: u32,
33 pub duration: u64,
35 #[builder(default = 1.0)]
37 pub rate: f32,
38 #[builder(default = 1.0)]
40 pub volume: f32,
41 #[builder(default)]
42 pub reserved: [u8; 10],
43 pub matrix: Option<[i32; 9]>,
45 #[builder(default = 0)]
47 pub preview_time: u32,
48 #[builder(default = 0)]
50 pub preview_duration: u32,
51 #[builder(default = 0)]
53 pub poster_time: u32,
54 #[builder(default = 0)]
56 pub selection_time: u32,
57 #[builder(default = 0)]
59 pub selection_duration: u32,
60 #[builder(default = 0)]
62 pub current_time: u32,
63 pub next_track_id: u32,
65}
66
67impl MovieHeaderAtom {
68 pub fn update_duration<F>(&mut self, mut closure: F) -> &mut Self
69 where
70 F: FnMut(Duration) -> Duration,
71 {
72 self.duration = scaled_duration(
73 closure(unscaled_duration(self.duration, u64::from(self.timescale))),
74 u64::from(self.timescale),
75 );
76 self
77 }
78
79 pub fn duration(&self) -> Duration {
80 unscaled_duration(self.duration, u64::from(self.timescale))
81 }
82}
83
84impl ParseAtomData for MovieHeaderAtom {
85 fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
86 crate::atom::util::parser::assert_atom_type!(atom_type, MVHD);
87 use crate::atom::util::parser::stream;
88 use winnow::Parser;
89 Ok(parser::parse_mvhd_data.parse(stream(input))?)
90 }
91}
92
93impl SerializeAtom for MovieHeaderAtom {
94 fn atom_type(&self) -> FourCC {
95 MVHD
96 }
97
98 fn into_body_bytes(self) -> Vec<u8> {
99 serializer::serialize_mvhd_atom(self)
100 }
101}
102
103mod serializer {
104 use crate::atom::util::serializer::{fixed_point_16x16, fixed_point_8x8};
105
106 use super::MovieHeaderAtom;
107
108 pub fn serialize_mvhd_atom(mvhd: MovieHeaderAtom) -> Vec<u8> {
109 let mut data = Vec::new();
110
111 let needs_64_bit = mvhd.creation_time > u64::from(u32::MAX)
113 || mvhd.modification_time > u64::from(u32::MAX)
114 || mvhd.duration > u64::from(u32::MAX);
115
116 let version: u8 = if needs_64_bit { 1 } else { 0 };
117
118 let be_u32_or_u64 = |v: u64| match version {
119 0 => u32::try_from(v).unwrap().to_be_bytes().to_vec(),
120 1 => v.to_be_bytes().to_vec(),
121 _ => unreachable!(),
122 };
123
124 data.extend(version.to_be_bytes());
125 data.extend(mvhd.flags);
126 data.extend(be_u32_or_u64(mvhd.creation_time));
127 data.extend(be_u32_or_u64(mvhd.modification_time));
128 data.extend(mvhd.timescale.to_be_bytes());
129 data.extend(be_u32_or_u64(mvhd.duration));
130 data.extend(fixed_point_16x16(mvhd.rate));
131 data.extend(fixed_point_8x8(mvhd.volume));
132 data.extend(mvhd.reserved);
133 if let Some(matrix) = mvhd.matrix {
134 for value in matrix {
135 data.extend(value.to_be_bytes());
136 }
137 }
138 data.extend_from_slice(&mvhd.preview_time.to_be_bytes());
139 data.extend_from_slice(&mvhd.preview_duration.to_be_bytes());
140 data.extend_from_slice(&mvhd.poster_time.to_be_bytes());
141 data.extend_from_slice(&mvhd.selection_time.to_be_bytes());
142 data.extend_from_slice(&mvhd.selection_duration.to_be_bytes());
143 data.extend_from_slice(&mvhd.current_time.to_be_bytes());
144 data.extend_from_slice(&mvhd.next_track_id.to_be_bytes());
145
146 data
147 }
148}
149
150mod parser {
151 use winnow::{
152 binary::{be_i32, be_u32, be_u64, u8},
153 combinator::{opt, seq, trace},
154 error::StrContext,
155 ModalResult, Parser,
156 };
157
158 use super::MovieHeaderAtom;
159 use crate::atom::util::parser::{
160 be_u32_as_u64, fixed_array, fixed_point_16x16, fixed_point_8x8, flags3, version_0_or_1,
161 Stream,
162 };
163
164 pub fn parse_mvhd_data(input: &mut Stream<'_>) -> ModalResult<MovieHeaderAtom> {
165 let be_u32_or_u64 = |version: u8| {
166 let be_u64_type_fix =
167 |input: &mut Stream<'_>| -> ModalResult<u64> { be_u64.parse_next(input) };
168 match version {
169 0 => be_u32_as_u64,
170 1 => be_u64_type_fix,
171 _ => unreachable!(),
172 }
173 };
174
175 trace(
176 "mvhd",
177 seq!(MovieHeaderAtom {
178 version: version_0_or_1,
179 flags: flags3,
180 creation_time: be_u32_or_u64(version).context(StrContext::Label("creation_time")),
181 modification_time: be_u32_or_u64(version)
182 .context(StrContext::Label("modification_time")),
183 timescale: be_u32.context(StrContext::Label("timescale")),
184 duration: be_u32_or_u64(version).context(StrContext::Label("duration")),
185 rate: fixed_point_16x16.context(StrContext::Label("rate")),
186 volume: fixed_point_8x8.context(StrContext::Label("volume")),
187 reserved: fixed_array(u8).context(StrContext::Label("reserved")),
188 matrix: opt(fixed_array(be_i32)).context(StrContext::Label("matrix")),
189 preview_time: be_u32.context(StrContext::Label("preview_time")),
190 preview_duration: be_u32.context(StrContext::Label("preview_duration")),
191 poster_time: be_u32.context(StrContext::Label("poster_time")),
192 selection_time: be_u32.context(StrContext::Label("selection_time")),
193 selection_duration: be_u32.context(StrContext::Label("selection_duration")),
194 current_time: be_u32.context(StrContext::Label("current_time")),
195 next_track_id: be_u32.context(StrContext::Label("next_track_id")),
196 }),
197 )
198 .parse_next(input)
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use crate::atom::test_utils::test_atom_roundtrip;
206
207 #[test]
209 fn test_mvhd_roundtrip() {
210 test_atom_roundtrip::<MovieHeaderAtom>(MVHD);
211 }
212}