Skip to main content

mp4/
reader.rs

1use std::collections::HashMap;
2use std::io::{Read, Seek};
3use std::time::Duration;
4
5use crate::meta::MetaBox;
6use crate::*;
7
8#[derive(Debug)]
9pub struct Mp4Reader<R> {
10    reader: R,
11    pub ftyp: FtypBox,
12    pub moov: MoovBox,
13    pub moofs: Vec<MoofBox>,
14    pub emsgs: Vec<EmsgBox>,
15
16    tracks: HashMap<u32, Mp4Track>,
17    size: u64,
18}
19
20impl<R: Read + Seek> Mp4Reader<R> {
21    pub fn read_header(mut reader: R, size: u64) -> Result<Self> {
22        let start = reader.stream_position()?;
23
24        let mut ftyp = None;
25        let mut moov = None;
26        let mut moofs = Vec::new();
27        let mut moof_offsets = Vec::new();
28        let mut emsgs = Vec::new();
29
30        let mut current = start;
31        while current < size {
32            // Get box header.
33            let header = BoxHeader::read(&mut reader)?;
34            let BoxHeader { name, size: s } = header;
35            if s > size {
36                return Err(Error::InvalidData(
37                    "file contains a box with a larger size than it",
38                ));
39            }
40
41            // Break if size zero BoxHeader, which can result in dead-loop.
42            if s == 0 {
43                break;
44            }
45
46            // Match and parse the atom boxes.
47            match name {
48                BoxType::FtypBox => {
49                    ftyp = Some(FtypBox::read_box(&mut reader, s)?);
50                }
51                BoxType::FreeBox => {
52                    skip_box(&mut reader, s)?;
53                }
54                BoxType::MdatBox => {
55                    skip_box(&mut reader, s)?;
56                }
57                BoxType::MoovBox => {
58                    moov = Some(MoovBox::read_box(&mut reader, s)?);
59                }
60                BoxType::MoofBox => {
61                    let moof_offset = reader.stream_position()? - 8;
62                    let moof = MoofBox::read_box(&mut reader, s)?;
63                    moofs.push(moof);
64                    moof_offsets.push(moof_offset);
65                }
66                BoxType::EmsgBox => {
67                    let emsg = EmsgBox::read_box(&mut reader, s)?;
68                    emsgs.push(emsg);
69                }
70                _ => {
71                    // XXX warn!()
72                    skip_box(&mut reader, s)?;
73                }
74            }
75            current = reader.stream_position()?;
76        }
77
78        if ftyp.is_none() {
79            return Err(Error::BoxNotFound(BoxType::FtypBox));
80        }
81        if moov.is_none() {
82            return Err(Error::BoxNotFound(BoxType::MoovBox));
83        }
84
85        let size = current - start;
86        let mut tracks = if let Some(ref moov) = moov {
87            if moov.traks.iter().any(|trak| trak.tkhd.track_id == 0) {
88                return Err(Error::InvalidData("illegal track id 0"));
89            }
90            moov.traks
91                .iter()
92                .map(|trak| (trak.tkhd.track_id, Mp4Track::from(trak)))
93                .collect()
94        } else {
95            HashMap::new()
96        };
97
98        // Update tracks if any fragmented (moof) boxes are found.
99        if !moofs.is_empty() {
100            let mut default_sample_duration = 0;
101            if let Some(ref moov) = moov {
102                if let Some(ref mvex) = &moov.mvex {
103                    default_sample_duration = mvex.trex.default_sample_duration
104                }
105            }
106
107            for (moof, moof_offset) in moofs.iter().zip(moof_offsets) {
108                for traf in moof.trafs.iter() {
109                    let track_id = traf.tfhd.track_id;
110                    if let Some(track) = tracks.get_mut(&track_id) {
111                        track.default_sample_duration = default_sample_duration;
112                        track.moof_offsets.push(moof_offset);
113                        track.trafs.push(traf.clone())
114                    } else {
115                        return Err(Error::TrakNotFound(track_id));
116                    }
117                }
118            }
119        }
120
121        Ok(Mp4Reader {
122            reader,
123            ftyp: ftyp.unwrap(),
124            moov: moov.unwrap(),
125            moofs,
126            emsgs,
127            size,
128            tracks,
129        })
130    }
131
132    pub fn read_fragment_header<FR: Read + Seek>(
133        &self,
134        mut reader: FR,
135        size: u64,
136    ) -> Result<Mp4Reader<FR>> {
137        let start = reader.stream_position()?;
138
139        let mut moofs = Vec::new();
140        let mut moof_offsets = Vec::new();
141
142        let mut current = start;
143        while current < size {
144            // Get box header.
145            let header = BoxHeader::read(&mut reader)?;
146            let BoxHeader { name, size: s } = header;
147            if s > size {
148                return Err(Error::InvalidData(
149                    "file contains a box with a larger size than it",
150                ));
151            }
152
153            // Break if size zero BoxHeader, which can result in dead-loop.
154            if s == 0 {
155                break;
156            }
157
158            // Match and parse the atom boxes.
159            match name {
160                BoxType::MdatBox => {
161                    skip_box(&mut reader, s)?;
162                }
163                BoxType::MoofBox => {
164                    let moof_offset = reader.stream_position()? - 8;
165                    let moof = MoofBox::read_box(&mut reader, s)?;
166                    moofs.push(moof);
167                    moof_offsets.push(moof_offset);
168                }
169                _ => {
170                    // XXX warn!()
171                    skip_box(&mut reader, s)?;
172                }
173            }
174            current = reader.stream_position()?;
175        }
176
177        if moofs.is_empty() {
178            return Err(Error::BoxNotFound(BoxType::MoofBox));
179        }
180
181        let size = current - start;
182        let mut tracks: HashMap<u32, Mp4Track> = self
183            .moov
184            .traks
185            .iter()
186            .map(|trak| (trak.tkhd.track_id, Mp4Track::from(trak)))
187            .collect();
188
189        let mut default_sample_duration = 0;
190        if let Some(ref mvex) = &self.moov.mvex {
191            default_sample_duration = mvex.trex.default_sample_duration
192        }
193
194        for (moof, moof_offset) in moofs.iter().zip(moof_offsets) {
195            for traf in moof.trafs.iter() {
196                let track_id = traf.tfhd.track_id;
197                if let Some(track) = tracks.get_mut(&track_id) {
198                    track.default_sample_duration = default_sample_duration;
199                    track.moof_offsets.push(moof_offset);
200                    track.trafs.push(traf.clone())
201                } else {
202                    return Err(Error::TrakNotFound(track_id));
203                }
204            }
205        }
206
207        Ok(Mp4Reader {
208            reader,
209            ftyp: self.ftyp.clone(),
210            moov: self.moov.clone(),
211            moofs,
212            emsgs: Vec::new(),
213            tracks,
214            size,
215        })
216    }
217
218    pub fn size(&self) -> u64 {
219        self.size
220    }
221
222    pub fn major_brand(&self) -> &FourCC {
223        &self.ftyp.major_brand
224    }
225
226    pub fn minor_version(&self) -> u32 {
227        self.ftyp.minor_version
228    }
229
230    pub fn compatible_brands(&self) -> &[FourCC] {
231        &self.ftyp.compatible_brands
232    }
233
234    pub fn duration(&self) -> Duration {
235        Duration::from_millis(self.moov.mvhd.duration * 1000 / self.moov.mvhd.timescale as u64)
236    }
237
238    pub fn timescale(&self) -> u32 {
239        self.moov.mvhd.timescale
240    }
241
242    pub fn is_fragmented(&self) -> bool {
243        !self.moofs.is_empty()
244    }
245
246    pub fn tracks(&self) -> &HashMap<u32, Mp4Track> {
247        &self.tracks
248    }
249
250    pub fn sample_count(&self, track_id: u32) -> Result<u32> {
251        if let Some(track) = self.tracks.get(&track_id) {
252            Ok(track.sample_count())
253        } else {
254            Err(Error::TrakNotFound(track_id))
255        }
256    }
257
258    pub fn read_sample(&mut self, track_id: u32, sample_id: u32) -> Result<Option<Mp4Sample>> {
259        if let Some(track) = self.tracks.get(&track_id) {
260            track.read_sample(&mut self.reader, sample_id)
261        } else {
262            Err(Error::TrakNotFound(track_id))
263        }
264    }
265
266    pub fn sample_time_duration(&mut self, track_id: u32, sample_id: u32) -> Result<(u64, u32)> {
267        if let Some(track) = self.tracks.get(&track_id) {
268            track.sample_time(sample_id)
269        } else {
270            Err(Error::TrakNotFound(track_id))
271        }
272    }
273
274    pub fn sample_offset(&mut self, track_id: u32, sample_id: u32) -> Result<u64> {
275        if let Some(track) = self.tracks.get(&track_id) {
276            track.sample_offset(sample_id)
277        } else {
278            Err(Error::TrakNotFound(track_id))
279        }
280    }
281}
282
283impl<R> Mp4Reader<R> {
284    pub fn metadata(&self) -> impl Metadata<'_> {
285        self.moov.udta.as_ref().and_then(|udta| {
286            udta.meta.as_ref().and_then(|meta| match meta {
287                MetaBox::Mdir { ilst } => ilst.as_ref(),
288                _ => None,
289            })
290        })
291    }
292}