Skip to main content

mp4_atom/moov/trak/mdia/minf/stbl/stsd/
tx3g.rs

1use crate::*;
2
3/// Text track sample description format for tx3g
4///
5/// 3GPP TS 26.245 or ETSI TS 126 245 Section 5.16
6/// See https://www.etsi.org/deliver/etsi_ts/126200_126299/126245/18.00.00_60/ts_126245v180000p.pdf
7#[derive(Debug, Clone, PartialEq, Eq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct Tx3g {
10    pub data_reference_index: u16,
11    pub display_flags: u32,
12    pub horizontal_justification: i8,
13    pub vertical_justification: i8,
14    pub bg_color_rgba: RgbaColor,
15    pub box_record: [i16; 4],
16    pub style_record: [u8; 12],
17    // Looks like this is supposed to be present, but we're relaxed about it.
18    pub ftab: Option<Ftab>,
19    // TODO: possibly there is a default disparity box here too, but never seen.
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Default)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct RgbaColor {
25    pub red: u8,
26    pub green: u8,
27    pub blue: u8,
28    pub alpha: u8,
29}
30
31impl Default for Tx3g {
32    fn default() -> Self {
33        Tx3g {
34            data_reference_index: 0,
35            display_flags: 0,
36            horizontal_justification: 1,
37            vertical_justification: -1,
38            bg_color_rgba: RgbaColor {
39                red: 0,
40                green: 0,
41                blue: 0,
42                alpha: 255,
43            },
44            box_record: [0, 0, 0, 0],
45            style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255],
46            ftab: None, // TODO: possibly a nice Serif?
47        }
48    }
49}
50
51impl Atom for Tx3g {
52    const KIND: FourCC = FourCC::new(b"tx3g");
53
54    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
55        u32::decode(buf)?; // reserved
56        u16::decode(buf)?; // reserved
57        let data_reference_index = u16::decode(buf)?;
58
59        let display_flags = u32::decode(buf)?;
60        let horizontal_justification = i8::decode(buf)?;
61        let vertical_justification = i8::decode(buf)?;
62        let bg_color_rgba = RgbaColor {
63            red: u8::decode(buf)?,
64            green: u8::decode(buf)?,
65            blue: u8::decode(buf)?,
66            alpha: u8::decode(buf)?,
67        };
68        let box_record: [i16; 4] = [
69            i16::decode(buf)?,
70            i16::decode(buf)?,
71            i16::decode(buf)?,
72            i16::decode(buf)?,
73        ];
74        let style_record = <[u8; 12]>::decode(buf)?;
75        let mut ftab = None; // TODO
76        while let Some(atom) = Any::decode_maybe(buf)? {
77            match atom {
78                Any::Ftab(atom) => ftab = atom.into(),
79                unknown => Self::decode_unknown(&unknown)?,
80            }
81        }
82        skip_trailing_padding(buf);
83
84        Ok(Tx3g {
85            data_reference_index,
86            display_flags,
87            horizontal_justification,
88            vertical_justification,
89            bg_color_rgba,
90            box_record,
91            style_record,
92            ftab,
93        })
94    }
95
96    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
97        0u32.encode(buf)?; // reserved
98        0u16.encode(buf)?; // reserved
99        self.data_reference_index.encode(buf)?;
100        self.display_flags.encode(buf)?;
101        self.horizontal_justification.encode(buf)?;
102        self.vertical_justification.encode(buf)?;
103        self.bg_color_rgba.red.encode(buf)?;
104        self.bg_color_rgba.green.encode(buf)?;
105        self.bg_color_rgba.blue.encode(buf)?;
106        self.bg_color_rgba.alpha.encode(buf)?;
107        for n in 0..4 {
108            (self.box_record[n]).encode(buf)?;
109        }
110        for n in 0..12 {
111            (self.style_record[n]).encode(buf)?;
112        }
113        self.ftab.encode(buf)?;
114
115        Ok(())
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_tx3g() {
125        let expected = Tx3g {
126            data_reference_index: 1,
127            display_flags: 0,
128            horizontal_justification: 1,
129            vertical_justification: -1,
130            bg_color_rgba: RgbaColor {
131                red: 0,
132                green: 0,
133                blue: 0,
134                alpha: 255,
135            },
136            box_record: [0, 0, 0, 0],
137            style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255],
138            ftab: None,
139        };
140        let mut buf = Vec::new();
141        expected.encode(&mut buf).unwrap();
142
143        let mut buf = buf.as_ref();
144        let decoded = Tx3g::decode(&mut buf).unwrap();
145        assert_eq!(decoded, expected);
146    }
147
148    // From the MPEG file format conformance test suite: isobmff/09_text.mp4
149    const ENCODED_TX3G_09: &[u8] = &[
150        0x00, 0x00, 0x00, 0x40, 0x74, 0x78, 0x33, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
151        0x01, 0x00, 0x04, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
152        0x00, 0x3c, 0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x12, 0xff, 0xff, 0xff,
153        0xff, 0x00, 0x00, 0x00, 0x12, 0x66, 0x74, 0x61, 0x62, 0x00, 0x01, 0x00, 0x01, 0x05, 0x53,
154        0x65, 0x72, 0x69, 0x66,
155    ];
156
157    /* From isobmff/09_text_gpac.json:
158           "@Size": "64",
159           "@Type": "tx3g",
160           "@Specification": "3gpp",
161           "@Container": "stsd",
162           "@dataReferenceIndex": "1",
163           "@displayFlags": "40000",
164           "@horizontal-justification": "1",
165           "@vertical-justification": "-1",
166           "@backgroundColor": "ff 0 0 ff",
167           "DefaultBox": {
168               "BoxRecord": {
169               "@top": "0",
170               "@left": "0",
171               "@bottom": "60",
172               "@right": "400"
173               },
174               "FontTableBox": {
175               "@Size": "18",
176               "@Type": "ftab",
177               "@Specification": "3gpp",
178               "@Container": "tx3g text enct",
179               "FontRecord": {
180                   "@ID": "1",
181                   "@name": "Serif"
182               }
183               }
184           },
185           "DefaultStyle": {
186               "StyleRecord": {
187               "@startChar": "0",
188               "@endChar": "0",
189               "@fontID": "1",
190               "@styles": "Normal",
191               "@fontSize": "18",
192               "@textColor": "ff ff ff ff"
193               }
194           },
195           "FontTableBox": {
196               "@Size": "18",
197               "@Type": "ftab",
198               "@Specification": "3gpp",
199               "@Container": "tx3g text enct",
200               "FontRecord": {
201               "@ID": "1",
202               "@name": "Serif"
203               }
204           }
205           },
206    */
207
208    fn get_reference_mpeg_09_tx3g() -> Tx3g {
209        Tx3g {
210            data_reference_index: 1,
211            display_flags: 0x00040000,
212            horizontal_justification: 1,
213            vertical_justification: -1,
214            bg_color_rgba: RgbaColor {
215                red: 0xFF,
216                green: 0x00,
217                blue: 0x00,
218                alpha: 0xFF,
219            },
220            box_record: [0, 0, 60, 400],
221            style_record: [0, 0, 0, 0, 0, 1, 0, 18, 255, 255, 255, 255],
222            ftab: Some(Ftab {
223                font_entries: vec![FontEntry {
224                    font_id: 1,
225                    font: "Serif".into(),
226                }],
227            }),
228        }
229    }
230    #[test]
231    fn test_mpeg_09_text_decode() {
232        let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_TX3G_09);
233        let tx3g = Tx3g::decode(buf).expect("failed to decode tx3g");
234        assert_eq!(tx3g, get_reference_mpeg_09_tx3g());
235    }
236
237    #[test]
238    fn test_mpeg_09_text_encode() {
239        let tx3g = get_reference_mpeg_09_tx3g();
240        let mut buf = Vec::new();
241        tx3g.encode(&mut buf).unwrap();
242        assert_eq!(buf.as_slice(), ENCODED_TX3G_09);
243    }
244}