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