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 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 if s == 0 {
43 break;
44 }
45
46 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 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 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 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 if s == 0 {
155 break;
156 }
157
158 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 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}