1use byteorder::{BigEndian, WriteBytesExt};
2use serde::Serialize;
3use std::io::Write;
4
5use crate::mp4box::*;
6
7pub enum TrackFlag {
8 TrackEnabled = 0x000001,
9 }
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
14pub struct TkhdBox {
15 pub version: u8,
16 pub flags: u32,
17 pub creation_time: u64,
18 pub modification_time: u64,
19 pub track_id: u32,
20 pub duration: u64,
21 pub layer: u16,
22 pub alternate_group: u16,
23
24 #[serde(with = "value_u8")]
25 pub volume: FixedPointU8,
26 pub matrix: Matrix,
27
28 #[serde(with = "value_u32")]
29 pub width: FixedPointU16,
30
31 #[serde(with = "value_u32")]
32 pub height: FixedPointU16,
33}
34
35impl Default for TkhdBox {
36 fn default() -> Self {
37 TkhdBox {
38 version: 0,
39 flags: TrackFlag::TrackEnabled as u32,
40 creation_time: 0,
41 modification_time: 0,
42 track_id: 0,
43 duration: 0,
44 layer: 0,
45 alternate_group: 0,
46 volume: FixedPointU8::new(1),
47 matrix: Matrix::default(),
48 width: FixedPointU16::new(0),
49 height: FixedPointU16::new(0),
50 }
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
55pub struct Matrix {
56 pub a: i32,
57 pub b: i32,
58 pub u: i32,
59 pub c: i32,
60 pub d: i32,
61 pub v: i32,
62 pub x: i32,
63 pub y: i32,
64 pub w: i32,
65}
66
67impl std::fmt::Display for Matrix {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 write!(
70 f,
71 "{:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x}",
72 self.a, self.b, self.u, self.c, self.d, self.v, self.x, self.y, self.w
73 )
74 }
75}
76
77impl Default for Matrix {
78 fn default() -> Self {
79 Self {
80 a: 0x00010000,
82 b: 0,
83 u: 0,
84 c: 0,
85 d: 0x00010000,
86 v: 0,
87 x: 0,
88 y: 0,
89 w: 0x40000000,
90 }
91 }
92}
93
94impl TkhdBox {
95 pub fn get_type(&self) -> BoxType {
96 BoxType::TkhdBox
97 }
98
99 pub fn get_size(&self) -> u64 {
100 let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
101 if self.version == 1 {
102 size += 32;
103 } else if self.version == 0 {
104 size += 20;
105 }
106 size += 60;
107 size
108 }
109
110 pub fn set_width(&mut self, width: u16) {
111 self.width = FixedPointU16::new(width);
112 }
113
114 pub fn set_height(&mut self, height: u16) {
115 self.height = FixedPointU16::new(height);
116 }
117}
118
119impl Mp4Box for TkhdBox {
120 const TYPE: BoxType = BoxType::TkhdBox;
121
122 fn box_size(&self) -> u64 {
123 self.get_size()
124 }
125
126 fn to_json(&self) -> Result<String, Error> {
127 Ok(serde_json::to_string(&self).unwrap())
128 }
129
130 fn summary(&self) -> Result<String, Error> {
131 let s = format!(
132 "creation_time={} track_id={} duration={} layer={} volume={} matrix={} width={} height={}",
133 self.creation_time,
134 self.track_id,
135 self.duration,
136 self.layer,
137 self.volume.value(),
138 self.matrix,
139 self.width.value(),
140 self.height.value()
141 );
142 Ok(s)
143 }
144}
145
146impl BlockReader for TkhdBox {
147 fn read_block<'a>(reader: &mut impl Reader<'a>) -> Result<Self, Error> {
148 let (version, flags) = read_box_header_ext(reader);
149
150 let (creation_time, modification_time, track_id, _, duration) = if version == 1 {
151 (
152 reader.get_u64(),
153 reader.get_u64(),
154 reader.get_u32(),
155 reader.get_u32(),
156 reader.get_u64(),
157 )
158 } else if version == 0 {
159 (
160 reader.get_u32() as u64,
161 reader.get_u32() as u64,
162 reader.get_u32(),
163 reader.get_u32(),
164 reader.get_u32() as u64,
165 )
166 } else {
167 return Err(Error::InvalidData("version must be 0 or 1"));
168 };
169
170 reader.get_u64(); let layer = reader.get_u16();
173 let alternate_group = reader.get_u16();
174 let volume = FixedPointU8::new_raw(reader.get_u16());
175
176 reader.get_u16(); let matrix = Matrix {
179 a: reader.get_i32(),
180 b: reader.get_i32(),
181 u: reader.get_i32(),
182 c: reader.get_i32(),
183 d: reader.get_i32(),
184 v: reader.get_i32(),
185 x: reader.get_i32(),
186 y: reader.get_i32(),
187 w: reader.get_i32(),
188 };
189
190 let width = FixedPointU16::new_raw(reader.get_u32());
191 let height = FixedPointU16::new_raw(reader.get_u32());
192
193 Ok(TkhdBox {
194 version,
195 flags,
196 creation_time,
197 modification_time,
198 track_id,
199 duration,
200 layer,
201 alternate_group,
202 volume,
203 matrix,
204 width,
205 height,
206 })
207 }
208
209 fn size_hint() -> usize {
210 84
211 }
212}
213
214impl<W: Write> WriteBox<&mut W> for TkhdBox {
215 fn write_box(&self, writer: &mut W) -> Result<u64, Error> {
216 let size = self.box_size();
217 BoxHeader::new(Self::TYPE, size).write(writer)?;
218
219 write_box_header_ext(writer, self.version, self.flags)?;
220
221 if self.version == 1 {
222 writer.write_u64::<BigEndian>(self.creation_time)?;
223 writer.write_u64::<BigEndian>(self.modification_time)?;
224 writer.write_u32::<BigEndian>(self.track_id)?;
225 writer.write_u32::<BigEndian>(0)?; writer.write_u64::<BigEndian>(self.duration)?;
227 } else if self.version == 0 {
228 writer.write_u32::<BigEndian>(self.creation_time as u32)?;
229 writer.write_u32::<BigEndian>(self.modification_time as u32)?;
230 writer.write_u32::<BigEndian>(self.track_id)?;
231 writer.write_u32::<BigEndian>(0)?; writer.write_u32::<BigEndian>(self.duration as u32)?;
233 } else {
234 return Err(Error::InvalidData("version must be 0 or 1"));
235 }
236
237 writer.write_u64::<BigEndian>(0)?; writer.write_u16::<BigEndian>(self.layer)?;
239 writer.write_u16::<BigEndian>(self.alternate_group)?;
240 writer.write_u16::<BigEndian>(self.volume.raw_value())?;
241
242 writer.write_u16::<BigEndian>(0)?; writer.write_i32::<BigEndian>(self.matrix.a)?;
245 writer.write_i32::<BigEndian>(self.matrix.b)?;
246 writer.write_i32::<BigEndian>(self.matrix.u)?;
247 writer.write_i32::<BigEndian>(self.matrix.c)?;
248 writer.write_i32::<BigEndian>(self.matrix.d)?;
249 writer.write_i32::<BigEndian>(self.matrix.v)?;
250 writer.write_i32::<BigEndian>(self.matrix.x)?;
251 writer.write_i32::<BigEndian>(self.matrix.y)?;
252 writer.write_i32::<BigEndian>(self.matrix.w)?;
253
254 writer.write_u32::<BigEndian>(self.width.raw_value())?;
255 writer.write_u32::<BigEndian>(self.height.raw_value())?;
256
257 Ok(size)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::mp4box::BoxHeader;
265
266 #[tokio::test]
267 async fn test_tkhd32() {
268 let src_box = TkhdBox {
269 version: 0,
270 flags: TrackFlag::TrackEnabled as u32,
271 creation_time: 100,
272 modification_time: 200,
273 track_id: 1,
274 duration: 634634,
275 layer: 0,
276 alternate_group: 0,
277 volume: FixedPointU8::new(1),
278 matrix: Matrix::default(),
279 width: FixedPointU16::new(512),
280 height: FixedPointU16::new(288),
281 };
282 let mut buf = Vec::new();
283 src_box.write_box(&mut buf).unwrap();
284 assert_eq!(buf.len(), src_box.box_size() as usize);
285
286 let mut reader = buf.as_slice();
287 let header = BoxHeader::read(&mut reader, &mut 0).await.unwrap().unwrap();
288 assert_eq!(header.kind, BoxType::TkhdBox);
289 assert_eq!(src_box.box_size(), header.size);
290
291 let dst_box = TkhdBox::read_block(&mut reader).unwrap();
292 assert_eq!(src_box, dst_box);
293 }
294
295 #[tokio::test]
296 async fn test_tkhd64() {
297 let src_box = TkhdBox {
298 version: 1,
299 flags: TrackFlag::TrackEnabled as u32,
300 creation_time: 100,
301 modification_time: 200,
302 track_id: 1,
303 duration: 634634,
304 layer: 0,
305 alternate_group: 0,
306 volume: FixedPointU8::new(1),
307 matrix: Matrix::default(),
308 width: FixedPointU16::new(512),
309 height: FixedPointU16::new(288),
310 };
311 let mut buf = Vec::new();
312 src_box.write_box(&mut buf).unwrap();
313 assert_eq!(buf.len(), src_box.box_size() as usize);
314
315 let mut reader = buf.as_slice();
316 let header = BoxHeader::read(&mut reader, &mut 0).await.unwrap().unwrap();
317 assert_eq!(header.kind, BoxType::TkhdBox);
318 assert_eq!(src_box.box_size(), header.size);
319
320 let dst_box = TkhdBox::read_block(&mut reader).unwrap();
321 assert_eq!(src_box, dst_box);
322 }
323}