scuffle_mp4/boxes/types/
tkhd.rs

1use std::io;
2
3use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
4use bytes::Bytes;
5use fixed::types::extra::{U8, U16};
6use fixed::{FixedI16, FixedI32};
7
8use crate::boxes::header::{BoxHeader, FullBoxHeader};
9use crate::boxes::traits::BoxType;
10
11#[derive(Debug, Clone, PartialEq)]
12/// Track Header Box
13/// ISO/IEC 14496-12:2022(E) - 8.3.2
14pub struct Tkhd {
15    pub header: FullBoxHeader,
16    pub creation_time: u64,
17    pub modification_time: u64,
18    pub track_id: u32,
19    pub reserved: u32,
20    pub duration: u64,
21    pub reserved2: [u32; 2],
22    pub layer: u16,
23    pub alternate_group: u16,
24    pub volume: FixedI16<U8>,
25    pub reserved3: u16,
26    pub matrix: [u32; 9],
27    pub width: FixedI32<U16>,
28    pub height: FixedI32<U16>,
29}
30
31impl Tkhd {
32    pub fn new(
33        creation_time: u64,
34        modification_time: u64,
35        track_id: u32,
36        duration: u64,
37        width_height: Option<(u32, u32)>,
38    ) -> Self {
39        let version = if creation_time > u32::MAX as u64 || modification_time > u32::MAX as u64 || duration > u32::MAX as u64
40        {
41            1
42        } else {
43            0
44        };
45
46        let (width, height) = width_height.unwrap_or((0, 0));
47        let volume = if width_height.is_some() {
48            FixedI16::<U8>::from_num(0)
49        } else {
50            FixedI16::<U8>::from_num(1)
51        };
52
53        Self {
54            header: FullBoxHeader::new(Self::NAME, version, Self::TRACK_ENABLED_FLAG | Self::TRACK_IN_MOVIE_FLAG),
55            creation_time,
56            modification_time,
57            track_id,
58            reserved: 0,
59            duration,
60            reserved2: [0; 2],
61            layer: 0,
62            alternate_group: 0,
63            volume,
64            reserved3: 0,
65            matrix: Self::MATRIX,
66            width: FixedI32::<U16>::from_num(width),
67            height: FixedI32::<U16>::from_num(height),
68        }
69    }
70}
71
72impl Tkhd {
73    pub const MATRIX: [u32; 9] = [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000];
74    pub const TRACK_ENABLED_FLAG: u32 = 0x000001;
75    pub const TRACK_IN_MOVIE_FLAG: u32 = 0x000002;
76    pub const TRACK_IN_PREVIEW_FLAG: u32 = 0x000004;
77    pub const TRACK_SIZE_IS_ASPECT_RATIO_FLAG: u32 = 0x000008;
78}
79
80impl BoxType for Tkhd {
81    const NAME: [u8; 4] = *b"tkhd";
82
83    fn demux(header: BoxHeader, data: Bytes) -> io::Result<Self> {
84        let mut reader = io::Cursor::new(data);
85
86        let header = FullBoxHeader::demux(header, &mut reader)?;
87
88        let (creation_time, modification_time, track_id, reserved, duration) = if header.version == 1 {
89            (
90                reader.read_u64::<BigEndian>()?, // creation_time
91                reader.read_u64::<BigEndian>()?, // modification_time
92                reader.read_u32::<BigEndian>()?, // track_id
93                reader.read_u32::<BigEndian>()?, // reserved
94                reader.read_u64::<BigEndian>()?, // duration
95            )
96        } else {
97            (
98                reader.read_u32::<BigEndian>()? as u64, // creation_time
99                reader.read_u32::<BigEndian>()? as u64, // modification_time
100                reader.read_u32::<BigEndian>()?,        // track_id
101                reader.read_u32::<BigEndian>()?,        // reserved
102                reader.read_u32::<BigEndian>()? as u64, // duration
103            )
104        };
105
106        let mut reserved2 = [0; 2];
107        for v in reserved2.iter_mut() {
108            *v = reader.read_u32::<BigEndian>()?;
109        }
110
111        let layer = reader.read_u16::<BigEndian>()?;
112        let alternate_group = reader.read_u16::<BigEndian>()?;
113        let volume = reader.read_i16::<BigEndian>()?;
114
115        let reserved3 = reader.read_u16::<BigEndian>()?;
116
117        let mut matrix = [0; 9];
118        for v in matrix.iter_mut() {
119            *v = reader.read_u32::<BigEndian>()?;
120        }
121
122        let width = reader.read_i32::<BigEndian>()?;
123        let height = reader.read_i32::<BigEndian>()?;
124
125        Ok(Self {
126            header,
127            creation_time,
128            modification_time,
129            track_id,
130            reserved,
131            duration,
132            reserved2,
133            layer,
134            alternate_group,
135            volume: FixedI16::<U8>::from_bits(volume),
136            reserved3,
137            matrix,
138            width: FixedI32::<U16>::from_bits(width),
139            height: FixedI32::<U16>::from_bits(height),
140        })
141    }
142
143    fn primitive_size(&self) -> u64 {
144        let mut size = self.header.size();
145        size += if self.header.version == 1 {
146            8 + 8 + 4 + 4 + 8 // creation_time, modification_time, track_id,
147        // reserved, duration
148        } else {
149            4 + 4 + 4 + 4 + 4 // creation_time, modification_time, track_id,
150            // reserved, duration
151        };
152
153        size += 4 * 2; // reserved2
154        size += 2; // layer
155        size += 2; // alternate_group
156        size += 2; // volume
157        size += 2; // reserved
158        size += 4 * 9; // matrix
159        size += 4; // width
160        size += 4; // height
161
162        size
163    }
164
165    fn primitive_mux<T: io::Write>(&self, writer: &mut T) -> io::Result<()> {
166        self.header.mux(writer)?;
167
168        if self.header.version == 1 {
169            writer.write_u64::<BigEndian>(self.creation_time)?;
170            writer.write_u64::<BigEndian>(self.modification_time)?;
171            writer.write_u32::<BigEndian>(self.track_id)?;
172            writer.write_u32::<BigEndian>(self.reserved)?;
173            writer.write_u64::<BigEndian>(self.duration)?;
174        } else {
175            writer.write_u32::<BigEndian>(self.creation_time as u32)?;
176            writer.write_u32::<BigEndian>(self.modification_time as u32)?;
177            writer.write_u32::<BigEndian>(self.track_id)?;
178            writer.write_u32::<BigEndian>(self.reserved)?;
179            writer.write_u32::<BigEndian>(self.duration as u32)?;
180        }
181
182        for v in self.reserved2.iter() {
183            writer.write_u32::<BigEndian>(*v)?;
184        }
185
186        writer.write_u16::<BigEndian>(self.layer)?;
187        writer.write_u16::<BigEndian>(self.alternate_group)?;
188        writer.write_i16::<BigEndian>(self.volume.to_bits())?;
189        writer.write_u16::<BigEndian>(self.reserved3)?;
190
191        for v in self.matrix.iter() {
192            writer.write_u32::<BigEndian>(*v)?;
193        }
194
195        writer.write_i32::<BigEndian>(self.width.to_bits())?;
196        writer.write_i32::<BigEndian>(self.height.to_bits())?;
197
198        Ok(())
199    }
200
201    fn validate(&self) -> io::Result<()> {
202        if self.header.version > 1 {
203            return Err(io::Error::new(io::ErrorKind::InvalidData, "tkhd version must be 0 or 1"));
204        }
205
206        if self.track_id == 0 {
207            return Err(io::Error::new(io::ErrorKind::InvalidData, "tkhd track_id must not be 0"));
208        }
209
210        if self.reserved != 0 {
211            return Err(io::Error::new(io::ErrorKind::InvalidData, "tkhd reserved must be 0"));
212        }
213
214        if self.reserved2 != [0; 2] {
215            return Err(io::Error::new(io::ErrorKind::InvalidData, "tkhd reserved2 must be 0"));
216        }
217
218        if self.reserved3 != 0 {
219            return Err(io::Error::new(io::ErrorKind::InvalidData, "tkhd reserved3 must be 0"));
220        }
221
222        if self.header.version == 0 {
223            if self.creation_time > u32::MAX as u64 {
224                return Err(io::Error::new(
225                    io::ErrorKind::InvalidData,
226                    "tkhd creation_time must be less than 2^32",
227                ));
228            }
229
230            if self.modification_time > u32::MAX as u64 {
231                return Err(io::Error::new(
232                    io::ErrorKind::InvalidData,
233                    "tkhd modification_time must be less than 2^32",
234                ));
235            }
236
237            if self.duration > u32::MAX as u64 {
238                return Err(io::Error::new(
239                    io::ErrorKind::InvalidData,
240                    "tkhd duration must be less than 2^32",
241                ));
242            }
243        }
244
245        Ok(())
246    }
247}