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
83        Ok(Tx3g {
84            data_reference_index,
85            display_flags,
86            horizontal_justification,
87            vertical_justification,
88            bg_color_rgba,
89            box_record,
90            style_record,
91            ftab,
92        })
93    }
94
95    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
96        0u32.encode(buf)?; // reserved
97        0u16.encode(buf)?; // reserved
98        self.data_reference_index.encode(buf)?;
99        self.display_flags.encode(buf)?;
100        self.horizontal_justification.encode(buf)?;
101        self.vertical_justification.encode(buf)?;
102        self.bg_color_rgba.red.encode(buf)?;
103        self.bg_color_rgba.green.encode(buf)?;
104        self.bg_color_rgba.blue.encode(buf)?;
105        self.bg_color_rgba.alpha.encode(buf)?;
106        for n in 0..4 {
107            (self.box_record[n]).encode(buf)?;
108        }
109        for n in 0..12 {
110            (self.style_record[n]).encode(buf)?;
111        }
112        self.ftab.encode(buf)?;
113
114        Ok(())
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_tx3g() {
124        let expected = Tx3g {
125            data_reference_index: 1,
126            display_flags: 0,
127            horizontal_justification: 1,
128            vertical_justification: -1,
129            bg_color_rgba: RgbaColor {
130                red: 0,
131                green: 0,
132                blue: 0,
133                alpha: 255,
134            },
135            box_record: [0, 0, 0, 0],
136            style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255],
137            ftab: None,
138        };
139        let mut buf = Vec::new();
140        expected.encode(&mut buf).unwrap();
141
142        let mut buf = buf.as_ref();
143        let decoded = Tx3g::decode(&mut buf).unwrap();
144        assert_eq!(decoded, expected);
145    }
146
147    // From the MPEG file format conformance test suite: isobmff/09_text.mp4
148    const ENCODED_TX3G_09: &[u8] = &[
149        0x00, 0x00, 0x00, 0x40, 0x74, 0x78, 0x33, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
150        0x01, 0x00, 0x04, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
151        0x00, 0x3c, 0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x12, 0xff, 0xff, 0xff,
152        0xff, 0x00, 0x00, 0x00, 0x12, 0x66, 0x74, 0x61, 0x62, 0x00, 0x01, 0x00, 0x01, 0x05, 0x53,
153        0x65, 0x72, 0x69, 0x66,
154    ];
155
156    /* From isobmff/09_text_gpac.json:
157           "@Size": "64",
158           "@Type": "tx3g",
159           "@Specification": "3gpp",
160           "@Container": "stsd",
161           "@dataReferenceIndex": "1",
162           "@displayFlags": "40000",
163           "@horizontal-justification": "1",
164           "@vertical-justification": "-1",
165           "@backgroundColor": "ff 0 0 ff",
166           "DefaultBox": {
167               "BoxRecord": {
168               "@top": "0",
169               "@left": "0",
170               "@bottom": "60",
171               "@right": "400"
172               },
173               "FontTableBox": {
174               "@Size": "18",
175               "@Type": "ftab",
176               "@Specification": "3gpp",
177               "@Container": "tx3g text enct",
178               "FontRecord": {
179                   "@ID": "1",
180                   "@name": "Serif"
181               }
182               }
183           },
184           "DefaultStyle": {
185               "StyleRecord": {
186               "@startChar": "0",
187               "@endChar": "0",
188               "@fontID": "1",
189               "@styles": "Normal",
190               "@fontSize": "18",
191               "@textColor": "ff ff ff ff"
192               }
193           },
194           "FontTableBox": {
195               "@Size": "18",
196               "@Type": "ftab",
197               "@Specification": "3gpp",
198               "@Container": "tx3g text enct",
199               "FontRecord": {
200               "@ID": "1",
201               "@name": "Serif"
202               }
203           }
204           },
205    */
206
207    fn get_reference_mpeg_09_tx3g() -> Tx3g {
208        Tx3g {
209            data_reference_index: 1,
210            display_flags: 0x00040000,
211            horizontal_justification: 1,
212            vertical_justification: -1,
213            bg_color_rgba: RgbaColor {
214                red: 0xFF,
215                green: 0x00,
216                blue: 0x00,
217                alpha: 0xFF,
218            },
219            box_record: [0, 0, 60, 400],
220            style_record: [0, 0, 0, 0, 0, 1, 0, 18, 255, 255, 255, 255],
221            ftab: Some(Ftab {
222                font_entries: vec![FontEntry {
223                    font_id: 1,
224                    font: "Serif".into(),
225                }],
226            }),
227        }
228    }
229    #[test]
230    fn test_mpeg_09_text_decode() {
231        let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_TX3G_09);
232        let tx3g = Tx3g::decode(buf).expect("failed to decode tx3g");
233        assert_eq!(tx3g, get_reference_mpeg_09_tx3g());
234    }
235
236    #[test]
237    fn test_mpeg_09_text_encode() {
238        let tx3g = get_reference_mpeg_09_tx3g();
239        let mut buf = Vec::new();
240        tx3g.encode(&mut buf).unwrap();
241        assert_eq!(buf.as_slice(), ENCODED_TX3G_09);
242    }
243}