1use bon::Builder;
2use std::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 TKHD: FourCC = FourCC::new(b"tkhd");
15
16const IDENTITY_MATRIX: [i32; 9] = [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000];
17
18#[derive(Default, Debug, Clone, Builder)]
19pub struct TrackHeaderAtom {
20 #[builder(default = 0)]
22 pub version: u8,
23 #[builder(default = [0, 0, 7])]
25 pub flags: [u8; 3],
26 #[builder(default = mp4_timestamp_now())]
28 pub creation_time: u64,
29 #[builder(default = mp4_timestamp_now())]
31 pub modification_time: u64,
32 pub track_id: u32,
34 pub duration: u64,
36 #[builder(default = 0)]
38 pub layer: i16,
39 #[builder(default = 0)]
41 pub alternate_group: i16,
42 #[builder(default = 1.0)]
44 pub volume: f32,
45 pub matrix: Option<[i32; 9]>,
49 #[builder(default = 0.0)]
51 pub width: f32,
52 #[builder(default = 0.0)]
54 pub height: f32,
55}
56
57impl TrackHeaderAtom {
58 pub fn duration(&self, movie_timescale: u64) -> Duration {
59 unscaled_duration(self.duration, movie_timescale)
60 }
61
62 pub fn update_duration<F>(&mut self, movie_timescale: u64, mut closure: F) -> &mut Self
63 where
64 F: FnMut(Duration) -> Duration,
65 {
66 self.duration = scaled_duration(closure(self.duration(movie_timescale)), movie_timescale);
67 self
68 }
69}
70
71impl ParseAtomData for TrackHeaderAtom {
72 fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
73 crate::atom::util::parser::assert_atom_type!(atom_type, TKHD);
74 use crate::atom::util::parser::stream;
75 use winnow::Parser;
76 Ok(parser::parse_tkhd_data.parse(stream(input))?)
77 }
78}
79
80impl SerializeAtom for TrackHeaderAtom {
81 fn atom_type(&self) -> FourCC {
82 TKHD
83 }
84
85 fn into_body_bytes(self) -> Vec<u8> {
86 serializer::serialize_tkhd_data(self)
87 }
88}
89
90mod serializer {
91 use crate::atom::{
92 tkhd::IDENTITY_MATRIX,
93 util::serializer::{fixed_point_16x16, fixed_point_8x8},
94 };
95
96 use super::TrackHeaderAtom;
97
98 pub fn serialize_tkhd_data(tkhd: TrackHeaderAtom) -> Vec<u8> {
99 let mut data = Vec::new();
100
101 let version: u8 = if tkhd.version == 1
102 || tkhd.creation_time > u64::from(u32::MAX)
103 || tkhd.modification_time > u64::from(u32::MAX)
104 || tkhd.duration > u64::from(u32::MAX)
105 {
106 1
107 } else {
108 0
109 };
110
111 let be_u32_or_u64 = |v: u64| match version {
112 0 => u32::try_from(v).unwrap().to_be_bytes().to_vec(),
113 1 => v.to_be_bytes().to_vec(),
114 _ => unreachable!(),
115 };
116
117 data.push(version);
118 data.extend(tkhd.flags);
119 data.extend(be_u32_or_u64(tkhd.creation_time));
120 data.extend(be_u32_or_u64(tkhd.modification_time));
121 data.extend(tkhd.track_id.to_be_bytes());
122 data.extend([0u8; 4]); data.extend(be_u32_or_u64(tkhd.duration));
124 data.extend([0u8; 8]); data.extend(tkhd.layer.to_be_bytes());
126 data.extend(tkhd.alternate_group.to_be_bytes());
127 data.extend(fixed_point_8x8(tkhd.volume));
128 data.extend([0u8; 2]); data.extend(
130 tkhd.matrix
131 .unwrap_or(IDENTITY_MATRIX)
132 .into_iter()
133 .flat_map(|v| v.to_be_bytes()),
134 );
135 data.extend(fixed_point_16x16(tkhd.width));
136 data.extend(fixed_point_16x16(tkhd.height));
137
138 data
139 }
140}
141
142mod parser {
143 use winnow::{
144 binary::{be_i16, be_i32, be_u32, be_u64},
145 combinator::{seq, trace},
146 error::{StrContext, StrContextValue},
147 ModalResult, Parser,
148 };
149
150 use super::TrackHeaderAtom;
151 use crate::atom::{
152 tkhd::IDENTITY_MATRIX,
153 util::parser::{
154 be_u32_as_u64, byte_array, fixed_array, fixed_point_16x16, fixed_point_8x8, flags3,
155 version, Stream,
156 },
157 };
158
159 pub fn parse_tkhd_data(input: &mut Stream<'_>) -> ModalResult<TrackHeaderAtom> {
160 let be_u32_or_u64 = |version: u8| {
161 let be_u64_type_fix =
162 |input: &mut Stream<'_>| -> ModalResult<u64> { be_u64.parse_next(input) };
163 match version {
164 0 => be_u32_as_u64,
165 1 => be_u64_type_fix,
166 _ => unreachable!(),
167 }
168 };
169
170 trace(
171 "tkhd",
172 seq!(TrackHeaderAtom {
173 version: version
174 .verify(|version| *version <= 1)
175 .context(StrContext::Expected(StrContextValue::Description(
176 "expected version 0 or 1"
177 ))),
178 flags: flags3,
179 creation_time: be_u32_or_u64(version).context(StrContext::Label("creation_time")),
180 modification_time: be_u32_or_u64(version).context(StrContext::Label("modification_time")),
181 track_id: be_u32.context(StrContext::Label("track_id")),
182 _: byte_array::<4>.context(StrContext::Label("reserved_1")),
183 duration: be_u32_or_u64(version),
184 _: byte_array::<8>.context(StrContext::Label("reserved_2")),
185 layer: be_i16.context(StrContext::Label("layer")),
186 alternate_group: be_i16.context(StrContext::Label("alternate_group")),
187 volume: fixed_point_8x8.context(StrContext::Label("volume")),
188 _: byte_array::<2>.context(StrContext::Label("reserved_3")),
189 matrix: matrix.context(StrContext::Label("matrix")),
190 width: fixed_point_16x16.context(StrContext::Label("width")),
191 height: fixed_point_16x16.context(StrContext::Label("height")),
192 })
193 .context(StrContext::Label("tkhd")),
194 )
195 .parse_next(input)
196 }
197
198 fn matrix(input: &mut Stream<'_>) -> ModalResult<Option<[i32; 9]>> {
199 trace(
200 "matrix",
201 fixed_array(be_i32).map(|matrix: [i32; 9]| {
202 if is_empty_matrix(&matrix) {
203 None
204 } else {
205 Some(matrix)
206 }
207 }),
208 )
209 .parse_next(input)
210 }
211
212 fn is_empty_matrix(matrix: &[i32; 9]) -> bool {
213 let empty = [0; 9];
214 let identity = IDENTITY_MATRIX;
215 matrix == &empty || matrix == &identity
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use crate::atom::test_utils::test_atom_roundtrip;
223
224 #[test]
226 fn test_tkhd_roundtrip() {
227 test_atom_roundtrip::<TrackHeaderAtom>(TKHD);
228 }
229}