flowly_mp4/mp4box/
tkhd.rs

1use byteorder::{BigEndian, WriteBytesExt};
2use serde::Serialize;
3use std::io::Write;
4
5use crate::mp4box::*;
6
7pub enum TrackFlag {
8    TrackEnabled = 0x000001,
9    // TrackInMovie = 0x000002,
10    // TrackInPreview = 0x000004,
11}
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            // unity matrix according to ISO/IEC 14496-12:2005(E)
81            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(); // reserved
171
172        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(); // reserved
177
178        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)?; // reserved
226            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)?; // reserved
232            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)?; // reserved
238        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)?; // reserved
243
244        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}