mp4_atom/meta/
iloc.rs

1use crate::*;
2
3// ItemInformationBox. ISO/IEC 14496-12:2022 Section 8.11.3
4// This is used to work out where the items are
5
6ext! {
7    name: Iloc,
8    versions: [0, 1, 2],
9    flags: {}
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ItemLocationExtent {
14    pub item_reference_index: u64,
15    pub offset: u64,
16    pub length: u64,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct ItemLocation {
21    pub item_id: u32,
22    pub construction_method: u8, // enum?
23    pub data_reference_index: u16,
24    pub base_offset: u64,
25    pub extents: Vec<ItemLocationExtent>,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct Iloc {
31    pub item_locations: Vec<ItemLocation>,
32}
33
34impl AtomExt for Iloc {
35    type Ext = IlocExt;
36
37    const KIND_EXT: FourCC = FourCC::new(b"iloc");
38
39    fn decode_body_ext<B: Buf>(buf: &mut B, ext: IlocExt) -> Result<Self> {
40        let sizes0 = u8::decode(buf)?;
41        let offset_size = sizes0 >> 4;
42        let length_size = sizes0 & 0x0F;
43        let sizes1 = u8::decode(buf)?;
44        let base_offset_size = sizes1 >> 4;
45        let index_size: u8 = if ext.version == IlocVersion::V1 || ext.version == IlocVersion::V2 {
46            sizes1 & 0x0F
47        } else {
48            0
49        };
50
51        let item_count = if ext.version == IlocVersion::V0 || ext.version == IlocVersion::V1 {
52            u16::decode(buf)? as usize
53        } else {
54            u32::decode(buf)? as usize
55        };
56        let mut item_locations = vec![];
57        for _i in 0..item_count {
58            let item_id = if ext.version == IlocVersion::V0 || ext.version == IlocVersion::V1 {
59                u16::decode(buf)? as u32
60            } else {
61                u32::decode(buf)?
62            };
63            let construction_method: u8 =
64                if ext.version == IlocVersion::V1 || ext.version == IlocVersion::V2 {
65                    let construction_method_packed = u16::decode(buf)?;
66                    (construction_method_packed & 0x0f) as u8
67                } else {
68                    0
69                };
70            let data_reference_index = u16::decode(buf)?;
71            let base_offset = match base_offset_size {
72                0 => 0u64,
73                4 => u32::decode(buf)? as u64,
74                8 => u64::decode(buf)?,
75                _ => panic!("iloc base_offset_size must be in [0,4,8]"),
76            };
77            let extent_count = u16::decode(buf)?;
78            let mut extents = vec![];
79            for _j in 0..extent_count {
80                let item_reference_index: u64 =
81                    if ext.version == IlocVersion::V1 || ext.version == IlocVersion::V2 {
82                        match index_size {
83                            0 => 0,
84                            4 => u32::decode(buf)? as u64,
85                            8 => u64::decode(buf)?,
86                            _ => panic!("iloc index_size must be in [0,4,8]"),
87                        }
88                    } else {
89                        0
90                    };
91                let extent_offset = match offset_size {
92                    0 => 0u64,
93                    4 => u32::decode(buf)? as u64,
94                    8 => u64::decode(buf)?,
95                    _ => panic!("iloc offset_size must be in [0,4,8]"),
96                };
97                let extent_length = match length_size {
98                    0 => 0u64,
99                    4 => u32::decode(buf)? as u64,
100                    8 => u64::decode(buf)?,
101                    _ => panic!("iloc length_size must be in [0,4,8]"),
102                };
103                extents.push(ItemLocationExtent {
104                    item_reference_index,
105                    offset: extent_offset,
106                    length: extent_length,
107                });
108            }
109            item_locations.push(ItemLocation {
110                item_id,
111                construction_method,
112                data_reference_index,
113                base_offset,
114                extents,
115            })
116        }
117        Ok(Iloc { item_locations })
118    }
119
120    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<IlocExt> {
121        let mut base_offset_size = 0u8;
122        // TODO: work out which version and sizes we really need for this instance.
123        for item_location in &self.item_locations {
124            if item_location.base_offset > 0 {
125                if item_location.base_offset > u32::MAX as u64 {
126                    base_offset_size = 8u8;
127                } else if base_offset_size != 8 {
128                    base_offset_size = 4u8;
129                }
130            }
131        }
132        let version = IlocVersion::V0;
133        let offset_size = 4u8;
134        let length_size = 4u8;
135
136        let index_size = 0u8;
137        let size0 = (offset_size << 4) | length_size;
138        let size1 = (base_offset_size << 4) | index_size;
139        size0.encode(buf)?;
140        size1.encode(buf)?;
141        if version == IlocVersion::V0 || version == IlocVersion::V1 {
142            (self.item_locations.len() as u16).encode(buf)?;
143        } else {
144            (self.item_locations.len() as u32).encode(buf)?;
145        }
146        for item_location in &self.item_locations {
147            if version == IlocVersion::V0 || version == IlocVersion::V1 {
148                (item_location.item_id as u16).encode(buf)?;
149            } else {
150                item_location.item_id.encode(buf)?;
151            }
152            if version == IlocVersion::V1 || version == IlocVersion::V2 {
153                (item_location.construction_method as u16).encode(buf)?
154            }
155            item_location.data_reference_index.encode(buf)?;
156            match base_offset_size {
157                0 => {}
158                4 => (item_location.base_offset as u32).encode(buf)?,
159                8 => item_location.base_offset.encode(buf)?,
160                _ => unreachable!("iloc base_offset_size must be in [0,4,8]"),
161            }
162            (item_location.extents.len() as u16).encode(buf)?;
163            for extent in &item_location.extents {
164                match index_size {
165                    0 => {}
166                    4 => (extent.item_reference_index as u32).encode(buf)?,
167                    8 => extent.item_reference_index.encode(buf)?,
168                    _ => unreachable!("iloc index_size must be in [0,4,8]"),
169                }
170                match offset_size {
171                    0 => {}
172                    4 => (extent.offset as u32).encode(buf)?,
173                    8 => extent.offset.encode(buf)?,
174                    _ => unreachable!("iloc offset_size must be in [0,4,8]"),
175                }
176                match length_size {
177                    0 => {}
178                    4 => (extent.length as u32).encode(buf)?,
179                    8 => extent.length.encode(buf)?,
180                    _ => unreachable!("iloc length_size must be in [0,4,8]"),
181                }
182            }
183        }
184        Ok(IlocExt {
185            version: IlocVersion::V0,
186        })
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    const ENCODED_ILOC_LIBAVIF: &[u8] = &[
195        0x00, 0x00, 0x00, 0x1e, 0x69, 0x6c, 0x6f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00,
196        0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x38, 0x00, 0x00, 0x00, 0x1a,
197    ];
198
199    #[test]
200    fn test_iloc_libavif_decode() {
201        let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_ILOC_LIBAVIF);
202
203        let iloc: Iloc = Iloc {
204            item_locations: vec![ItemLocation {
205                item_id: 1,
206                construction_method: 0,
207                data_reference_index: 0,
208                base_offset: 0,
209                extents: vec![ItemLocationExtent {
210                    item_reference_index: 0,
211                    offset: 312,
212                    length: 26,
213                }],
214            }],
215        };
216        let decoded = Iloc::decode(buf).unwrap();
217        assert_eq!(decoded, iloc);
218    }
219
220    #[test]
221    fn test_iloc_avif_encode() {
222        let iloc: Iloc = Iloc {
223            item_locations: vec![ItemLocation {
224                item_id: 1,
225                construction_method: 0,
226                data_reference_index: 0,
227                base_offset: 0,
228                extents: vec![ItemLocationExtent {
229                    item_reference_index: 0,
230                    offset: 312,
231                    length: 26,
232                }],
233            }],
234        };
235        let mut buf = Vec::new();
236        iloc.encode(&mut buf).unwrap();
237
238        assert_eq!(buf.as_slice(), ENCODED_ILOC_LIBAVIF);
239    }
240}