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    pub disp: Option<Disp>,
20}
21
22/// `DisparityBox` ('disp'): the stereo disparity shift of timed text. In a
23/// sample entry it is the default, in a sample it overrides the default.
24///
25/// See 3GPP TS 26.245 Section 5.17.1.9.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct Disp {
29    /// 16 times the shift applied to the left on the left view and to the
30    /// right on the right view.
31    pub disparity_shift_in_16th_pel: i16,
32}
33
34impl Atom for Disp {
35    const KIND: FourCC = FourCC::new(b"disp");
36
37    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
38        Ok(Self {
39            disparity_shift_in_16th_pel: i16::decode(buf)?,
40        })
41    }
42
43    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
44        self.disparity_shift_in_16th_pel.encode(buf)
45    }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Default)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50pub struct RgbaColor {
51    pub red: u8,
52    pub green: u8,
53    pub blue: u8,
54    pub alpha: u8,
55}
56
57impl Decode for RgbaColor {
58    fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
59        Ok(Self {
60            red: u8::decode(buf)?,
61            green: u8::decode(buf)?,
62            blue: u8::decode(buf)?,
63            alpha: u8::decode(buf)?,
64        })
65    }
66}
67
68impl Encode for RgbaColor {
69    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
70        self.red.encode(buf)?;
71        self.green.encode(buf)?;
72        self.blue.encode(buf)?;
73        self.alpha.encode(buf)
74    }
75}
76
77impl Default for Tx3g {
78    fn default() -> Self {
79        Tx3g {
80            data_reference_index: 0,
81            display_flags: 0,
82            horizontal_justification: 1,
83            vertical_justification: -1,
84            bg_color_rgba: RgbaColor {
85                red: 0,
86                green: 0,
87                blue: 0,
88                alpha: 255,
89            },
90            box_record: [0, 0, 0, 0],
91            style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255],
92            ftab: None, // TODO: possibly a nice Serif?
93            disp: None,
94        }
95    }
96}
97
98impl Atom for Tx3g {
99    const KIND: FourCC = FourCC::new(b"tx3g");
100
101    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
102        u32::decode(buf)?; // reserved
103        u16::decode(buf)?; // reserved
104        let data_reference_index = u16::decode(buf)?;
105
106        let display_flags = u32::decode(buf)?;
107        let horizontal_justification = i8::decode(buf)?;
108        let vertical_justification = i8::decode(buf)?;
109        let bg_color_rgba = RgbaColor::decode(buf)?;
110        let box_record: [i16; 4] = [
111            i16::decode(buf)?,
112            i16::decode(buf)?,
113            i16::decode(buf)?,
114            i16::decode(buf)?,
115        ];
116        let style_record = <[u8; 12]>::decode(buf)?;
117        let mut ftab = None;
118        let mut disp = None;
119        while let Some(atom) = Any::decode_maybe(buf)? {
120            match atom {
121                Any::Ftab(atom) => ftab = atom.into(),
122                Any::Disp(atom) => disp = atom.into(),
123                unknown => Self::decode_unknown(&unknown)?,
124            }
125        }
126        skip_trailing_padding(buf);
127
128        Ok(Tx3g {
129            data_reference_index,
130            display_flags,
131            horizontal_justification,
132            vertical_justification,
133            bg_color_rgba,
134            box_record,
135            style_record,
136            ftab,
137            disp,
138        })
139    }
140
141    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
142        0u32.encode(buf)?; // reserved
143        0u16.encode(buf)?; // reserved
144        self.data_reference_index.encode(buf)?;
145        self.display_flags.encode(buf)?;
146        self.horizontal_justification.encode(buf)?;
147        self.vertical_justification.encode(buf)?;
148        self.bg_color_rgba.encode(buf)?;
149        for n in 0..4 {
150            (self.box_record[n]).encode(buf)?;
151        }
152        for n in 0..12 {
153            (self.style_record[n]).encode(buf)?;
154        }
155        self.ftab.encode(buf)?;
156        self.disp.encode(buf)?;
157
158        Ok(())
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_tx3g() {
168        let expected = Tx3g {
169            data_reference_index: 1,
170            display_flags: 0,
171            horizontal_justification: 1,
172            vertical_justification: -1,
173            bg_color_rgba: RgbaColor {
174                red: 0,
175                green: 0,
176                blue: 0,
177                alpha: 255,
178            },
179            box_record: [0, 0, 0, 0],
180            style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255],
181            ftab: None,
182            disp: None,
183        };
184        let mut buf = Vec::new();
185        expected.encode(&mut buf).unwrap();
186
187        let mut buf = buf.as_ref();
188        let decoded = Tx3g::decode(&mut buf).unwrap();
189        assert_eq!(decoded, expected);
190    }
191
192    // From the MPEG file format conformance test suite: isobmff/09_text.mp4
193    const ENCODED_TX3G_09: &[u8] = &[
194        0x00, 0x00, 0x00, 0x40, 0x74, 0x78, 0x33, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
195        0x01, 0x00, 0x04, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
196        0x00, 0x3c, 0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x12, 0xff, 0xff, 0xff,
197        0xff, 0x00, 0x00, 0x00, 0x12, 0x66, 0x74, 0x61, 0x62, 0x00, 0x01, 0x00, 0x01, 0x05, 0x53,
198        0x65, 0x72, 0x69, 0x66,
199    ];
200
201    /* From isobmff/09_text_gpac.json:
202           "@Size": "64",
203           "@Type": "tx3g",
204           "@Specification": "3gpp",
205           "@Container": "stsd",
206           "@dataReferenceIndex": "1",
207           "@displayFlags": "40000",
208           "@horizontal-justification": "1",
209           "@vertical-justification": "-1",
210           "@backgroundColor": "ff 0 0 ff",
211           "DefaultBox": {
212               "BoxRecord": {
213               "@top": "0",
214               "@left": "0",
215               "@bottom": "60",
216               "@right": "400"
217               },
218               "FontTableBox": {
219               "@Size": "18",
220               "@Type": "ftab",
221               "@Specification": "3gpp",
222               "@Container": "tx3g text enct",
223               "FontRecord": {
224                   "@ID": "1",
225                   "@name": "Serif"
226               }
227               }
228           },
229           "DefaultStyle": {
230               "StyleRecord": {
231               "@startChar": "0",
232               "@endChar": "0",
233               "@fontID": "1",
234               "@styles": "Normal",
235               "@fontSize": "18",
236               "@textColor": "ff ff ff ff"
237               }
238           },
239           "FontTableBox": {
240               "@Size": "18",
241               "@Type": "ftab",
242               "@Specification": "3gpp",
243               "@Container": "tx3g text enct",
244               "FontRecord": {
245               "@ID": "1",
246               "@name": "Serif"
247               }
248           }
249           },
250    */
251
252    fn get_reference_mpeg_09_tx3g() -> Tx3g {
253        Tx3g {
254            data_reference_index: 1,
255            display_flags: 0x00040000,
256            horizontal_justification: 1,
257            vertical_justification: -1,
258            bg_color_rgba: RgbaColor {
259                red: 0xFF,
260                green: 0x00,
261                blue: 0x00,
262                alpha: 0xFF,
263            },
264            box_record: [0, 0, 60, 400],
265            style_record: [0, 0, 0, 0, 0, 1, 0, 18, 255, 255, 255, 255],
266            ftab: Some(Ftab {
267                font_entries: vec![FontEntry {
268                    font_id: 1,
269                    font: "Serif".into(),
270                }],
271            }),
272            disp: None,
273        }
274    }
275
276    #[test]
277    fn test_tx3g_with_default_disparity() {
278        let expected = Tx3g {
279            disp: Some(Disp {
280                disparity_shift_in_16th_pel: 48,
281            }),
282            ..get_reference_mpeg_09_tx3g()
283        };
284        let mut buf = Vec::new();
285        expected.encode(&mut buf).unwrap();
286        let decoded = Tx3g::decode(&mut buf.as_slice()).expect("failed to decode tx3g");
287        assert_eq!(decoded, expected);
288    }
289
290    #[test]
291    fn test_mpeg_09_text_decode() {
292        let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_TX3G_09);
293        let tx3g = Tx3g::decode(buf).expect("failed to decode tx3g");
294        assert_eq!(tx3g, get_reference_mpeg_09_tx3g());
295    }
296
297    #[test]
298    fn test_mpeg_09_text_encode() {
299        let tx3g = get_reference_mpeg_09_tx3g();
300        let mut buf = Vec::new();
301        tx3g.encode(&mut buf).unwrap();
302        assert_eq!(buf.as_slice(), ENCODED_TX3G_09);
303    }
304}