Skip to main content

mp4_edit/atom/leaf/
dref.rs

1use std::string::FromUtf8Error;
2
3use bon::Builder;
4
5use crate::{atom::FourCC, parser::ParseAtomData, writer::SerializeAtom, ParseError};
6
7pub const DREF: FourCC = FourCC::new(b"dref");
8
9/// Data Reference Entry Types
10pub mod entry_types {
11    /// URL data reference
12    pub const URL: &[u8; 4] = b"url ";
13    /// URN data reference
14    pub const URN: &[u8; 4] = b"urn ";
15    /// Alias data reference (Mac OS)
16    pub const ALIS: &[u8; 4] = b"alis";
17}
18
19/// Data Reference Entry Flags
20pub mod flags {
21    /// Media data is in the same file as the Movie Atom
22    pub const SELF_CONTAINED: u32 = 0x000001;
23}
24
25#[derive(Debug, Clone)]
26pub enum DataReferenceEntryInner {
27    Url(String),
28    Urn(String),
29    Alias(Vec<u8>),
30    Unknown(FourCC, Vec<u8>),
31}
32
33impl DataReferenceEntryInner {
34    fn new(entry_type: FourCC, data: Vec<u8>) -> Result<Self, FromUtf8Error> {
35        Ok(match &entry_type.0 {
36            entry_types::URL => DataReferenceEntryInner::Url(String::from_utf8(data)?),
37            entry_types::URN => DataReferenceEntryInner::Urn(String::from_utf8(data)?),
38            entry_types::ALIS => DataReferenceEntryInner::Alias(data),
39            _ => DataReferenceEntryInner::Unknown(entry_type, data),
40        })
41    }
42}
43
44/// A single data reference entry
45#[derive(Debug, Clone, Builder)]
46pub struct DataReferenceEntry {
47    /// Entry data/type (URL string, URN, alias data, etc.)
48    #[builder(setters(vis = ""))]
49    pub inner: DataReferenceEntryInner,
50    /// Version of the entry format
51    #[builder(default)]
52    pub version: u8,
53    /// Entry flags
54    #[builder(default)]
55    pub flags: [u8; 3],
56}
57
58impl<S: data_reference_entry_builder::State> DataReferenceEntryBuilder<S> {
59    pub fn url(
60        self,
61        url: impl Into<String>,
62    ) -> DataReferenceEntryBuilder<data_reference_entry_builder::SetInner<S>>
63    where
64        S::Inner: data_reference_entry_builder::IsUnset,
65    {
66        self.inner(DataReferenceEntryInner::Url(url.into()))
67    }
68
69    pub fn urn(
70        self,
71        urn: impl Into<String>,
72    ) -> DataReferenceEntryBuilder<data_reference_entry_builder::SetInner<S>>
73    where
74        S::Inner: data_reference_entry_builder::IsUnset,
75    {
76        self.inner(DataReferenceEntryInner::Urn(urn.into()))
77    }
78}
79
80impl DataReferenceEntry {
81    /// Check if this entry has the self-contained flag set
82    pub fn is_self_contained(&self) -> bool {
83        let flags_u32 = u32::from_be_bytes([0, self.flags[0], self.flags[1], self.flags[2]]);
84        (flags_u32 & flags::SELF_CONTAINED) != 0
85    }
86}
87
88/// Data Reference Atom (dref) - ISO/IEC 14496-12
89/// Contains a table of data references that declare the location(s) of the media data
90#[derive(Debug, Clone, Builder)]
91pub struct DataReferenceAtom {
92    /// Version of the dref atom format
93    #[builder(default = 0)]
94    pub version: u8,
95    /// Atom flags
96    #[builder(default = [0u8; 3])]
97    pub flags: [u8; 3],
98    /// Data reference entries
99    #[builder(with = FromIterator::from_iter)]
100    pub entries: Vec<DataReferenceEntry>,
101}
102
103impl<S: data_reference_atom_builder::State> DataReferenceAtomBuilder<S> {
104    pub fn entry(
105        self,
106        entry: DataReferenceEntry,
107    ) -> DataReferenceAtomBuilder<data_reference_atom_builder::SetEntries<S>>
108    where
109        S::Entries: data_reference_atom_builder::IsUnset,
110    {
111        self.entries(vec![entry])
112    }
113}
114
115impl ParseAtomData for DataReferenceAtom {
116    fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
117        crate::atom::util::parser::assert_atom_type!(atom_type, DREF);
118        use crate::atom::util::parser::stream;
119        use winnow::Parser;
120        Ok(parser::parse_dref_data.parse(stream(input))?)
121    }
122}
123
124impl SerializeAtom for DataReferenceAtom {
125    fn atom_type(&self) -> FourCC {
126        DREF
127    }
128
129    fn into_body_bytes(self) -> Vec<u8> {
130        serializer::serialize_dref_data(self)
131    }
132}
133
134mod serializer {
135    use crate::atom::{
136        dref::{entry_types, DataReferenceEntry, DataReferenceEntryInner},
137        DataReferenceAtom,
138    };
139
140    pub fn serialize_dref_data(data: DataReferenceAtom) -> Vec<u8> {
141        let entries = data.entries;
142        vec![
143            version(data.version),
144            flags(data.flags),
145            entry_count(entries.len()),
146            entries.into_iter().flat_map(entry).collect(),
147        ]
148        .into_iter()
149        .flatten()
150        .collect()
151    }
152
153    fn version(version: u8) -> Vec<u8> {
154        vec![version]
155    }
156
157    fn flags(flags: [u8; 3]) -> Vec<u8> {
158        flags.to_vec()
159    }
160
161    fn entry_count(n: usize) -> Vec<u8> {
162        u32::try_from(n)
163            .expect("entries len must fit in a u32")
164            .to_be_bytes()
165            .to_vec()
166    }
167
168    fn entry(e: DataReferenceEntry) -> Vec<u8> {
169        let e = raw_entry(e);
170        let data: Vec<u8> = vec![version(e.version), flags(e.flags), e.data]
171            .into_iter()
172            .flatten()
173            .collect();
174        let header_size = 4 + 4; // size + type
175        vec![
176            entry_size(header_size + data.len()).to_vec(),
177            e.typ.to_vec(),
178            data,
179        ]
180        .into_iter()
181        .flatten()
182        .collect()
183    }
184
185    fn entry_size(n: usize) -> [u8; 4] {
186        u32::try_from(n)
187            .expect("entry size len must fit in a u32")
188            .to_be_bytes()
189    }
190
191    struct RawEntry {
192        version: u8,
193        flags: [u8; 3],
194        typ: [u8; 4],
195        data: Vec<u8>,
196    }
197
198    fn raw_entry(e: DataReferenceEntry) -> RawEntry {
199        let (typ, data) = match e.inner {
200            DataReferenceEntryInner::Url(url) => (*entry_types::URL, url.into_bytes()),
201            DataReferenceEntryInner::Urn(urn) => (*entry_types::URN, urn.into_bytes()),
202            DataReferenceEntryInner::Alias(alias_data) => (*entry_types::ALIS, alias_data),
203            DataReferenceEntryInner::Unknown(typ, unknown_data) => (typ.0, unknown_data),
204        };
205        RawEntry {
206            version: e.version,
207            flags: e.flags,
208            typ,
209            data,
210        }
211    }
212}
213
214mod parser {
215    use winnow::{
216        binary::length_repeat,
217        combinator::{seq, trace},
218        error::StrContext,
219        Parser,
220    };
221
222    use super::{DataReferenceAtom, DataReferenceEntry, DataReferenceEntryInner};
223    use crate::atom::util::parser::{
224        atom_size, be_u32_as_usize, combinators::inclusive_length_and_then, flags3, fourcc,
225        rest_vec, version, Stream,
226    };
227
228    pub fn parse_dref_data(input: &mut Stream<'_>) -> winnow::ModalResult<DataReferenceAtom> {
229        trace(
230            "dref",
231            (version, flags3, entries)
232                .map(|(version, flags, entries)| DataReferenceAtom {
233                    version,
234                    flags,
235                    entries,
236                })
237                .context(StrContext::Label("dref")),
238        )
239        .parse_next(input)
240    }
241
242    fn entries(input: &mut Stream<'_>) -> winnow::ModalResult<Vec<DataReferenceEntry>> {
243        trace(
244            "entries",
245            length_repeat(
246                be_u32_as_usize.context(StrContext::Label("entry_count")),
247                entry.context(StrContext::Label("entry")),
248            ),
249        )
250        .parse_next(input)
251    }
252
253    fn entry(input: &mut Stream<'_>) -> winnow::ModalResult<DataReferenceEntry> {
254        trace(
255            "entry",
256            inclusive_length_and_then(atom_size, move |input: &mut Stream<'_>| {
257                let typ = fourcc.parse_next(input)?;
258                seq!(DataReferenceEntry {
259                    version: version,
260                    flags: flags3,
261                    inner: rest_vec
262                        .try_map(|data| DataReferenceEntryInner::new(typ, data))
263                        .context(StrContext::Label("data")),
264                })
265                .parse_next(input)
266            }),
267        )
268        .parse_next(input)
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275    use crate::atom::test_utils::test_atom_roundtrip;
276
277    /// Test round-trip for all available dref test data files
278    #[test]
279    fn test_dref_roundtrip() {
280        test_atom_roundtrip::<DataReferenceAtom>(DREF);
281    }
282}