Skip to main content

mp4/mp4box/
tkhd.rs

1use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
2use serde::Serialize;
3use std::io::{Read, Seek, 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    fn box_type(&self) -> BoxType {
121        self.get_type()
122    }
123
124    fn box_size(&self) -> u64 {
125        self.get_size()
126    }
127
128    fn to_json(&self) -> Result<String> {
129        Ok(serde_json::to_string(&self).unwrap())
130    }
131
132    fn summary(&self) -> Result<String> {
133        let s = format!(
134            "creation_time={} track_id={} duration={} layer={} volume={} matrix={} width={} height={}",
135            self.creation_time,
136            self.track_id,
137            self.duration,
138            self.layer,
139            self.volume.value(),
140            self.matrix,
141            self.width.value(),
142            self.height.value()
143        );
144        Ok(s)
145    }
146}
147
148impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
149    fn read_box(reader: &mut R, size: u64) -> Result<Self> {
150        let start = box_start(reader)?;
151
152        let (version, flags) = read_box_header_ext(reader)?;
153
154        let (creation_time, modification_time, track_id, _, duration) = if version == 1 {
155            (
156                reader.read_u64::<BigEndian>()?,
157                reader.read_u64::<BigEndian>()?,
158                reader.read_u32::<BigEndian>()?,
159                reader.read_u32::<BigEndian>()?,
160                reader.read_u64::<BigEndian>()?,
161            )
162        } else if version == 0 {
163            (
164                reader.read_u32::<BigEndian>()? as u64,
165                reader.read_u32::<BigEndian>()? as u64,
166                reader.read_u32::<BigEndian>()?,
167                reader.read_u32::<BigEndian>()?,
168                reader.read_u32::<BigEndian>()? as u64,
169            )
170        } else {
171            return Err(Error::InvalidData("version must be 0 or 1"));
172        };
173        reader.read_u64::<BigEndian>()?; // reserved
174        let layer = reader.read_u16::<BigEndian>()?;
175        let alternate_group = reader.read_u16::<BigEndian>()?;
176        let volume = FixedPointU8::new_raw(reader.read_u16::<BigEndian>()?);
177
178        reader.read_u16::<BigEndian>()?; // reserved
179        let matrix = Matrix {
180            a: reader.read_i32::<BigEndian>()?,
181            b: reader.read_i32::<BigEndian>()?,
182            u: reader.read_i32::<BigEndian>()?,
183            c: reader.read_i32::<BigEndian>()?,
184            d: reader.read_i32::<BigEndian>()?,
185            v: reader.read_i32::<BigEndian>()?,
186            x: reader.read_i32::<BigEndian>()?,
187            y: reader.read_i32::<BigEndian>()?,
188            w: reader.read_i32::<BigEndian>()?,
189        };
190
191        let width = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
192        let height = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
193
194        skip_bytes_to(reader, start + size)?;
195
196        Ok(TkhdBox {
197            version,
198            flags,
199            creation_time,
200            modification_time,
201            track_id,
202            duration,
203            layer,
204            alternate_group,
205            volume,
206            matrix,
207            width,
208            height,
209        })
210    }
211}
212
213impl<W: Write> WriteBox<&mut W> for TkhdBox {
214    fn write_box(&self, writer: &mut W) -> Result<u64> {
215        let size = self.box_size();
216        BoxHeader::new(self.box_type(), size).write(writer)?;
217
218        write_box_header_ext(writer, self.version, self.flags)?;
219
220        if self.version == 1 {
221            writer.write_u64::<BigEndian>(self.creation_time)?;
222            writer.write_u64::<BigEndian>(self.modification_time)?;
223            writer.write_u32::<BigEndian>(self.track_id)?;
224            writer.write_u32::<BigEndian>(0)?; // reserved
225            writer.write_u64::<BigEndian>(self.duration)?;
226        } else if self.version == 0 {
227            writer.write_u32::<BigEndian>(self.creation_time as u32)?;
228            writer.write_u32::<BigEndian>(self.modification_time as u32)?;
229            writer.write_u32::<BigEndian>(self.track_id)?;
230            writer.write_u32::<BigEndian>(0)?; // reserved
231            writer.write_u32::<BigEndian>(self.duration as u32)?;
232        } else {
233            return Err(Error::InvalidData("version must be 0 or 1"));
234        }
235
236        writer.write_u64::<BigEndian>(0)?; // reserved
237        writer.write_u16::<BigEndian>(self.layer)?;
238        writer.write_u16::<BigEndian>(self.alternate_group)?;
239        writer.write_u16::<BigEndian>(self.volume.raw_value())?;
240
241        writer.write_u16::<BigEndian>(0)?; // reserved
242
243        writer.write_i32::<BigEndian>(self.matrix.a)?;
244        writer.write_i32::<BigEndian>(self.matrix.b)?;
245        writer.write_i32::<BigEndian>(self.matrix.u)?;
246        writer.write_i32::<BigEndian>(self.matrix.c)?;
247        writer.write_i32::<BigEndian>(self.matrix.d)?;
248        writer.write_i32::<BigEndian>(self.matrix.v)?;
249        writer.write_i32::<BigEndian>(self.matrix.x)?;
250        writer.write_i32::<BigEndian>(self.matrix.y)?;
251        writer.write_i32::<BigEndian>(self.matrix.w)?;
252
253        writer.write_u32::<BigEndian>(self.width.raw_value())?;
254        writer.write_u32::<BigEndian>(self.height.raw_value())?;
255
256        Ok(size)
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263    use crate::mp4box::BoxHeader;
264    use std::io::Cursor;
265
266    #[test]
267    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 = Cursor::new(&buf);
287        let header = BoxHeader::read(&mut reader).unwrap();
288        assert_eq!(header.name, BoxType::TkhdBox);
289        assert_eq!(src_box.box_size(), header.size);
290
291        let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap();
292        assert_eq!(src_box, dst_box);
293    }
294
295    #[test]
296    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 = Cursor::new(&buf);
316        let header = BoxHeader::read(&mut reader).unwrap();
317        assert_eq!(header.name, BoxType::TkhdBox);
318        assert_eq!(src_box.box_size(), header.size);
319
320        let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap();
321        assert_eq!(src_box, dst_box);
322    }
323}