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_boxstring(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_boxstring(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
92fn decode_boxstring<B: Buf>(buf: &mut B) -> Result<String> {
93    let remaining_bytes = buf.remaining();
94    let body = &mut buf.slice(remaining_bytes);
95    let text = String::from_utf8(body.to_vec()).map_err(|_| Error::InvalidSize)?;
96    buf.advance(remaining_bytes);
97    Ok(text)
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    // from MPEG File Format Conformance suite, 16_vtt.mp4
105    const ENCODED_WVTT: &[u8] = &[
106        0x00, 0x00, 0x00, 0x3d, 0x77, 0x76, 0x74, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
107        0x01, 0x00, 0x00, 0x00, 0x2d, 0x76, 0x74, 0x74, 0x43, 0x57, 0x45, 0x42, 0x56, 0x54, 0x54,
108        0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x20, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x20, 0x68, 0x65, 0x61,
109        0x64, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e,
110        0x0a,
111    ];
112
113    #[test]
114    fn test_wvtt_decode() {
115        let buf: &mut std::io::Cursor<&[u8]> = &mut std::io::Cursor::new(ENCODED_WVTT);
116
117        let wvtt = Wvtt::decode(buf).expect("failed to decode wvtt");
118
119        assert_eq!(
120            wvtt,
121            Wvtt {
122                plaintext: PlainText {
123                    data_reference_index: 1
124                },
125                config: VttC {
126                    config: "WEBVTT\nSome dummy header information\n".into()
127                },
128                label: None,
129                btrt: None,
130            }
131        );
132    }
133
134    #[test]
135    fn test_wvtt_encode() {
136        let wvtt = Wvtt {
137            plaintext: PlainText {
138                data_reference_index: 1,
139            },
140            config: VttC {
141                config: "WEBVTT\nSome dummy header information\n".into(),
142            },
143            label: None,
144            btrt: None,
145        };
146
147        let mut buf = Vec::new();
148        wvtt.encode(&mut buf).unwrap();
149
150        assert_eq!(buf.as_slice(), ENCODED_WVTT);
151    }
152
153    #[test]
154    fn test_round_trip_with_label() {
155        let wvtt = Wvtt {
156            plaintext: PlainText {
157                data_reference_index: 1,
158            },
159            config: VttC {
160                config: "WEBVTT\nSome dummy header information\n".into(),
161            },
162            label: Some(Vlab {
163                source_label: "uri://dummy/label".into(),
164            }),
165            btrt: Some(Btrt {
166                buffer_size_db: 1,
167                max_bitrate: 2000,
168                avg_bitrate: 400,
169            }),
170        };
171
172        let mut buf = Vec::new();
173        wvtt.encode(&mut buf).unwrap();
174        let decoded = Wvtt::decode(&mut buf.as_ref()).expect("failed to decode wvtt");
175
176        assert_eq!(decoded, wvtt,);
177    }
178}