Skip to main content

mp4_atom/meta/ilst/
desc.rs

1use crate::*;
2
3/// An iTunes-style `desc` (description) metadata item.
4///
5/// Like the other iTunes `ilst` text items ([`Copyright`], [`Tool`]), the value
6/// is normally wrapped in a nested `data` atom (`desc → data → text`); FFmpeg
7/// also emits a "short" style where the value follows the item header directly.
8/// Both layouts are handled by the shared [`data`](super::data) codec. Decoding
9/// the `data`-wrapped form as a bare string (as the previous implementation did)
10/// consumed only up to the first NUL of the `data` box's own length field and
11/// failed the whole `moov` with `UnderDecode(desc)`.
12#[derive(Debug, Clone, PartialEq, Eq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct Desc {
15    pub country_indicator: u16,
16    pub language_indicator: u16,
17    pub text: String,
18}
19
20impl Atom for Desc {
21    const KIND: FourCC = FourCC::new(b"desc");
22
23    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
24        let data = super::data::decode_text(buf)?;
25        Ok(Desc {
26            country_indicator: data.country_indicator,
27            language_indicator: data.language_indicator,
28            text: data.text,
29        })
30    }
31
32    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
33        super::data::encode_text(
34            self.country_indicator,
35            self.language_indicator,
36            &self.text,
37            buf,
38        )
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    // Long QuickTime/GPAC/iTunes style: the value is wrapped in a nested `data`
47    // atom. Decoding this as a raw string previously failed with
48    // `UnderDecode(desc)` (the `data` box's length starts with a NUL).
49    const ENCODED_DESC_LONG: &[u8] = &[
50        0x00, 0x00, 0x00, 0x1E, // desc size = 30
51        b'd', b'e', b's', b'c', //
52        0x00, 0x00, 0x00, 0x16, // data size = 22
53        b'd', b'a', b't', b'a', //
54        0x00, 0x00, 0x00, 0x01, // type indicator: UTF-8
55        0x00, 0x00, 0x00, 0x00, // country + language
56        b'a', b' ', b'd', b'e', b's', b'c',
57    ];
58
59    #[test]
60    fn test_desc_long_style() {
61        let decoded = Desc::decode(&mut &ENCODED_DESC_LONG[..]).unwrap();
62        assert_eq!(decoded.country_indicator, 0);
63        assert_eq!(decoded.language_indicator, 0);
64        assert_eq!(decoded.text, "a desc");
65
66        // The encoder always emits the long `data`-wrapped layout.
67        let mut out = Vec::new();
68        decoded.encode(&mut out).unwrap();
69        assert_eq!(out, ENCODED_DESC_LONG);
70    }
71
72    // Short FFmpeg style: the UTF-8 value follows the item header directly.
73    #[test]
74    fn test_desc_short_style() {
75        let buf = [
76            0x00, 0x00, 0x00, 0x16, // desc size = 22
77            b'd', b'e', b's', b'c', //
78            0x00, 0x00, 0x00, 0x01, // type indicator (short style): UTF-8
79            0x00, 0x00, 0x00, 0x00, // country + language
80            b'a', b' ', b'd', b'e', b's', b'c',
81        ];
82        let decoded = Desc::decode(&mut &buf[..]).unwrap();
83        assert_eq!(decoded.country_indicator, 0);
84        assert_eq!(decoded.language_indicator, 0);
85        assert_eq!(decoded.text, "a desc");
86    }
87}