Skip to main content

mp4_atom/moov/trak/
tkhd.rs

1use crate::*;
2
3ext! {
4    name: Tkhd,
5    versions: [0, 1],
6    flags: {
7        track_enabled = 0,
8        track_in_movie = 1,
9        track_in_preview = 2,
10        track_size_is_aspect_ratio = 3,
11    }
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Default)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct Tkhd {
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    pub enabled: bool,
24    pub in_movie: bool,
25    /// When set, `width`/`height` express the track's suggested aspect ratio
26    /// rather than pixel dimensions (ISO/IEC 14496-12:2022 §8.3.2.3).
27    pub size_is_aspect_ratio: bool,
28
29    pub volume: FixedPoint<u8>,
30    pub matrix: Matrix,
31
32    pub width: FixedPoint<u16>,
33    pub height: FixedPoint<u16>,
34}
35
36impl AtomExt for Tkhd {
37    const KIND_EXT: FourCC = FourCC::new(b"tkhd");
38
39    type Ext = TkhdExt;
40
41    fn decode_body_ext<B: Buf>(buf: &mut B, ext: TkhdExt) -> Result<Self> {
42        let (creation_time, modification_time, track_id, _, duration) = match ext.version {
43            TkhdVersion::V1 => (
44                u64::decode(buf)?,
45                u64::decode(buf)?,
46                u32::decode(buf)?,
47                u32::decode(buf)?,
48                u64::decode(buf)?,
49            ),
50            TkhdVersion::V0 => (
51                u32::decode(buf)? as u64,
52                u32::decode(buf)? as u64,
53                u32::decode(buf)?,
54                u32::decode(buf)?,
55                u32::decode(buf)? as u64,
56            ),
57        };
58
59        u64::decode(buf)?; // reserved
60        let layer = u16::decode(buf)?;
61        let alternate_group = u16::decode(buf)?;
62        let volume = FixedPoint::decode(buf)?;
63
64        u16::decode(buf)?; // reserved
65        let matrix = Matrix::decode(buf)?;
66        let width = FixedPoint::decode(buf)?;
67        let height = FixedPoint::decode(buf)?;
68
69        Ok(Tkhd {
70            creation_time,
71            modification_time,
72            track_id,
73            duration,
74            layer,
75            alternate_group,
76            volume,
77            matrix,
78            width,
79            height,
80            enabled: ext.track_enabled,
81            in_movie: ext.track_in_movie,
82            size_is_aspect_ratio: ext.track_size_is_aspect_ratio,
83            // track_in_preview has no assigned meaning and is ignored on read
84            // per ISO/IEC 14496-12:2022 §8.3.2.3.
85        })
86    }
87
88    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<TkhdExt> {
89        self.creation_time.encode(buf)?;
90        self.modification_time.encode(buf)?;
91        self.track_id.encode(buf)?;
92        0u32.encode(buf)?; // reserved
93        self.duration.encode(buf)?;
94
95        0u64.encode(buf)?; // reserved
96        self.layer.encode(buf)?;
97        self.alternate_group.encode(buf)?;
98        self.volume.encode(buf)?;
99        0u16.encode(buf)?; // reserved
100        self.matrix.encode(buf)?;
101
102        self.width.encode(buf)?;
103        self.height.encode(buf)?;
104
105        Ok(TkhdExt {
106            version: TkhdVersion::V1,
107            track_enabled: self.enabled,
108            track_in_movie: self.in_movie,
109            // track_in_preview has no assigned meaning; per ISO/IEC
110            // 14496-12:2022 §8.3.2.3 writers should mirror track_in_movie.
111            track_in_preview: self.in_movie,
112            track_size_is_aspect_ratio: self.size_is_aspect_ratio,
113        })
114    }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
119pub struct Matrix {
120    pub a: i32,
121    pub b: i32,
122    pub u: i32,
123    pub c: i32,
124    pub d: i32,
125    pub v: i32,
126    pub x: i32,
127    pub y: i32,
128    pub w: i32,
129}
130
131impl Decode for Matrix {
132    fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
133        Ok(Self {
134            a: i32::decode(buf)?,
135            b: i32::decode(buf)?,
136            u: i32::decode(buf)?,
137            c: i32::decode(buf)?,
138            d: i32::decode(buf)?,
139            v: i32::decode(buf)?,
140            x: i32::decode(buf)?,
141            y: i32::decode(buf)?,
142            w: i32::decode(buf)?,
143        })
144    }
145}
146
147impl Encode for Matrix {
148    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
149        self.a.encode(buf)?;
150        self.b.encode(buf)?;
151        self.u.encode(buf)?;
152        self.c.encode(buf)?;
153        self.d.encode(buf)?;
154        self.v.encode(buf)?;
155        self.x.encode(buf)?;
156        self.y.encode(buf)?;
157        self.w.encode(buf)
158    }
159}
160
161impl std::fmt::Display for Matrix {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        write!(
164            f,
165            "{:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x}",
166            self.a, self.b, self.u, self.c, self.d, self.v, self.x, self.y, self.w
167        )
168    }
169}
170
171impl Default for Matrix {
172    fn default() -> Self {
173        Self {
174            // unity matrix according to ISO/IEC 14496-12:2005(E)
175            a: 0x00010000,
176            b: 0,
177            u: 0,
178            c: 0,
179            d: 0x00010000,
180            v: 0,
181            x: 0,
182            y: 0,
183            w: 0x40000000,
184        }
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_tkhd32() {
194        let expected = Tkhd {
195            creation_time: 100,
196            modification_time: 200,
197            track_id: 1,
198            duration: 634634,
199            layer: 0,
200            alternate_group: 0,
201            volume: 1.into(),
202            matrix: Matrix::default(),
203            width: 512.into(),
204            height: 288.into(),
205            enabled: true,
206            in_movie: true,
207            size_is_aspect_ratio: false,
208        };
209        let mut buf = Vec::new();
210        expected.encode(&mut buf).unwrap();
211
212        let mut buf = buf.as_ref();
213        let decoded = Tkhd::decode(&mut buf).unwrap();
214        assert_eq!(decoded, expected);
215    }
216
217    #[test]
218    fn test_tkhd64() {
219        let expected = Tkhd {
220            creation_time: 100,
221            modification_time: 200,
222            track_id: 1,
223            duration: 634634,
224            layer: 0,
225            alternate_group: 0,
226            volume: 1.into(),
227            matrix: Matrix::default(),
228            width: 512.into(),
229            height: 288.into(),
230            enabled: true,
231            in_movie: true,
232            size_is_aspect_ratio: false,
233        };
234        let mut buf = Vec::new();
235        expected.encode(&mut buf).unwrap();
236
237        let mut buf = buf.as_ref();
238        let decoded = Tkhd::decode(&mut buf).unwrap();
239        assert_eq!(decoded, expected);
240    }
241}