Skip to main content

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

1use crate::*;
2
3// WebVTTConfigurationBox
4#[derive(Debug, Clone, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct VttC {
7    pub config: String,
8}
9
10impl Atom for VttC {
11    const KIND: FourCC = FourCC::new(b"vttC");
12
13    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
14        Ok(Self {
15            config: decode_utf8_string(buf)?,
16        })
17    }
18
19    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
20        self.config.as_bytes().encode(buf)
21    }
22}
23
24// WebVTTSourceLabelBox
25#[derive(Debug, Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct Vlab {
28    pub source_label: String,
29}
30
31impl Atom for Vlab {
32    const KIND: FourCC = FourCC::new(b"vlab");
33
34    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
35        Ok(Self {
36            source_label: decode_utf8_string(buf)?,
37        })
38    }
39
40    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
41        // boxstring is just UTF-8 encoded string
42        self.source_label.as_bytes().encode(buf)
43    }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub struct Wvtt {
49    pub plaintext: PlainText,
50    pub config: VttC,
51    pub label: Option<Vlab>,
52    pub btrt: Option<Btrt>,
53}
54
55impl Atom for Wvtt {
56    const KIND: FourCC = FourCC::new(b"wvtt");
57
58    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
59        let plaintext = PlainText::decode(buf)?;
60
61        let mut vtcc = None;
62        let mut vlab = None;
63        let mut btrt = None;
64
65        while let Some(atom) = Any::decode_maybe(buf)? {
66            match atom {
67                Any::VttC(atom) => vtcc = atom.into(),
68                Any::Vlab(atom) => vlab = atom.into(),
69                Any::Btrt(atom) => btrt = atom.into(),
70                unknown => Self::decode_unknown(&unknown)?,
71            }
72        }
73        skip_trailing_padding(buf);
74
75        Ok(Self {
76            plaintext,
77            config: vtcc.ok_or(Error::MissingBox(VttC::KIND))?,
78            label: vlab,
79            btrt,
80        })
81    }
82
83    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
84        self.plaintext.encode(buf)?;
85        self.config.encode(buf)?;
86        self.label.encode(buf)?;
87        self.btrt.encode(buf)?;
88        Ok(())
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    // from MPEG File Format Conformance suite, 16_vtt.mp4
97    const ENCODED_WVTT: &[u8] = &[
98        0x00, 0x00, 0x00, 0x3d, 0x77, 0x76, 0x74, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
99        0x01, 0x00, 0x00, 0x00, 0x2d, 0x76, 0x74, 0x74, 0x43, 0x57, 0x45, 0x42, 0x56, 0x54, 0x54,
100        0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x20, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x20, 0x68, 0x65, 0x61,
101        0x64, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e,
102        0x0a,
103    ];
104
105    #[test]
106    fn test_wvtt_decode() {
107        let buf: &mut std::io::Cursor<&[u8]> = &mut std::io::Cursor::new(ENCODED_WVTT);
108
109        let wvtt = Wvtt::decode(buf).expect("failed to decode wvtt");
110
111        assert_eq!(
112            wvtt,
113            Wvtt {
114                plaintext: PlainText {
115                    data_reference_index: 1
116                },
117                config: VttC {
118                    config: "WEBVTT\nSome dummy header information\n".into()
119                },
120                label: None,
121                btrt: None,
122            }
123        );
124    }
125
126    #[test]
127    fn test_wvtt_encode() {
128        let wvtt = Wvtt {
129            plaintext: PlainText {
130                data_reference_index: 1,
131            },
132            config: VttC {
133                config: "WEBVTT\nSome dummy header information\n".into(),
134            },
135            label: None,
136            btrt: None,
137        };
138
139        let mut buf = Vec::new();
140        wvtt.encode(&mut buf).unwrap();
141
142        assert_eq!(buf.as_slice(), ENCODED_WVTT);
143    }
144
145    #[test]
146    fn test_round_trip_with_label() {
147        let wvtt = Wvtt {
148            plaintext: PlainText {
149                data_reference_index: 1,
150            },
151            config: VttC {
152                config: "WEBVTT\nSome dummy header information\n".into(),
153            },
154            label: Some(Vlab {
155                source_label: "uri://dummy/label".into(),
156            }),
157            btrt: Some(Btrt {
158                buffer_size_db: 1,
159                max_bitrate: 2000,
160                avg_bitrate: 400,
161            }),
162        };
163
164        let mut buf = Vec::new();
165        wvtt.encode(&mut buf).unwrap();
166        let decoded = Wvtt::decode(&mut buf.as_ref()).expect("failed to decode wvtt");
167
168        assert_eq!(decoded, wvtt,);
169    }
170}