Skip to main content

nom_exif/
parser.rs

1use std::{
2    cmp::{max, min},
3    fmt::{Debug, Display},
4    fs::File,
5    io::{self, Read, Seek},
6    marker::PhantomData,
7    net::TcpStream,
8    ops::Range,
9    path::Path,
10};
11
12use crate::{
13    buffer::Buffers,
14    error::{ParsedError, ParsingError, ParsingErrorState},
15    exif::{parse_exif_iter, TiffHeader},
16    file::Mime,
17    partial_vec::PartialVec,
18    skip::Skip,
19    video::parse_track_info,
20    ExifIter, Seekable, TrackInfo, Unseekable,
21};
22
23/// `MediaSource` represents a media data source that can be parsed by
24/// [`MediaParser`].
25///
26/// - Use `MediaSource::file_path(path)` or `MediaSource::file(file)` to create
27///   a MediaSource from a file
28///
29/// - Use `MediaSource::tcp_stream(stream)` to create a MediaSource from a `TcpStream`
30/// - In other cases:
31///
32///   - Use `MediaSource::seekable(reader)` to create a MediaSource from a `Read + Seek`
33///   
34///   - Use `MediaSource::unseekable(reader)` to create a MediaSource from a
35///     reader that only impl `Read`
36///   
37/// `seekable` is preferred to `unseekable`, since the former is more efficient
38/// when the parser needs to skip a large number of bytes.
39///
40/// Passing in a `BufRead` should be avoided because [`MediaParser`] comes with
41/// its own buffer management and the buffers can be shared between multiple
42/// parsing tasks, thus avoiding frequent memory allocations.
43
44pub struct MediaSource<R, S = Seekable> {
45    pub(crate) reader: R,
46    pub(crate) buf: Vec<u8>,
47    pub(crate) mime: Mime,
48    phantom: PhantomData<S>,
49}
50
51impl<R, S: Skip<R>> Debug for MediaSource<R, S> {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("MediaSource")
54            // .field("reader", &self.reader)
55            .field("mime", &self.mime)
56            .field("seekable", &S::debug())
57            .finish_non_exhaustive()
58    }
59}
60
61// Should be enough for parsing header
62const HEADER_PARSE_BUF_SIZE: usize = 128;
63
64impl<R: Read, S: Skip<R>> MediaSource<R, S> {
65    fn build(mut reader: R) -> crate::Result<Self> {
66        // TODO: reuse MediaParser to parse header
67        let mut buf = Vec::with_capacity(HEADER_PARSE_BUF_SIZE);
68        reader
69            .by_ref()
70            .take(HEADER_PARSE_BUF_SIZE as u64)
71            .read_to_end(&mut buf)?;
72        let mime: Mime = buf.as_slice().try_into()?;
73        Ok(Self {
74            reader,
75            buf,
76            mime,
77            phantom: PhantomData,
78        })
79    }
80
81    pub fn has_track(&self) -> bool {
82        match self.mime {
83            Mime::Image(_) => false,
84            Mime::Video(_) => true,
85        }
86    }
87
88    pub fn has_exif(&self) -> bool {
89        match self.mime {
90            Mime::Image(_) => true,
91            Mime::Video(_) => false,
92        }
93    }
94}
95
96impl<R: Read + Seek> MediaSource<R, Seekable> {
97    pub fn seekable(reader: R) -> crate::Result<Self> {
98        Self::build(reader)
99    }
100}
101
102impl<R: Read> MediaSource<R, Unseekable> {
103    pub fn unseekable(reader: R) -> crate::Result<Self> {
104        Self::build(reader)
105    }
106}
107
108impl MediaSource<File, Seekable> {
109    pub fn file_path<P: AsRef<Path>>(path: P) -> crate::Result<Self> {
110        Self::seekable(File::open(path)?)
111    }
112
113    pub fn file(file: File) -> crate::Result<Self> {
114        Self::seekable(file)
115    }
116}
117
118impl MediaSource<TcpStream, Unseekable> {
119    pub fn tcp_stream(stream: TcpStream) -> crate::Result<Self> {
120        Self::unseekable(stream)
121    }
122}
123
124// Keep align with 4K
125pub(crate) const INIT_BUF_SIZE: usize = 4096;
126pub(crate) const MIN_GROW_SIZE: usize = 4096;
127// Max size of APP1 is 0xFFFF
128pub(crate) const MAX_GROW_SIZE: usize = 63 * 1024;
129
130pub(crate) trait Buf {
131    fn buffer(&self) -> &[u8];
132    fn clear(&mut self);
133
134    fn set_position(&mut self, pos: usize);
135    #[allow(unused)]
136    fn position(&self) -> usize;
137}
138
139#[derive(Debug, Clone)]
140pub(crate) enum ParsingState {
141    TiffHeader(TiffHeader),
142    HeifExifSize(usize),
143}
144
145impl Display for ParsingState {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        match self {
148            ParsingState::TiffHeader(h) => Display::fmt(&format!("ParsingState: {h:?})"), f),
149            ParsingState::HeifExifSize(n) => Display::fmt(&format!("ParsingState: {n}"), f),
150        }
151    }
152}
153
154pub(crate) trait BufParser: Buf + Debug {
155    fn fill_buf<R: Read>(&mut self, reader: &mut R, size: usize) -> io::Result<usize>;
156    fn load_and_parse<R: Read, S: Skip<R>, P, O>(
157        &mut self,
158        reader: &mut R,
159        mut parse: P,
160    ) -> Result<O, ParsedError>
161    where
162        P: FnMut(&[u8], Option<ParsingState>) -> Result<O, ParsingErrorState>,
163    {
164        self.load_and_parse_with_offset::<R, S, _, _>(
165            reader,
166            |data, _, state| parse(data, state),
167            0,
168        )
169    }
170
171    #[tracing::instrument(skip_all)]
172    fn load_and_parse_with_offset<R: Read, S: Skip<R>, P, O>(
173        &mut self,
174        reader: &mut R,
175        mut parse: P,
176        offset: usize,
177    ) -> Result<O, ParsedError>
178    where
179        P: FnMut(&[u8], usize, Option<ParsingState>) -> Result<O, ParsingErrorState>,
180    {
181        if offset >= self.buffer().len() {
182            self.fill_buf(reader, MIN_GROW_SIZE)?;
183        }
184
185        let mut parsing_state: Option<ParsingState> = None;
186        loop {
187            let res = parse(self.buffer(), offset, parsing_state.take());
188            match res {
189                Ok(o) => return Ok(o),
190                Err(es) => {
191                    tracing::debug!(?es);
192                    parsing_state = es.state;
193
194                    match es.err {
195                        ParsingError::ClearAndSkip(n) => {
196                            self.clear_and_skip::<R, S>(reader, n)?;
197                        }
198                        ParsingError::Need(i) => {
199                            tracing::debug!(need = i, "need more bytes");
200                            let to_read = max(i, MIN_GROW_SIZE);
201                            let to_read = min(to_read, MAX_GROW_SIZE);
202
203                            let n = self.fill_buf(reader, to_read)?;
204                            if n == 0 {
205                                return Err(ParsedError::NoEnoughBytes);
206                            }
207                            tracing::debug!(n, "actual read");
208                        }
209                        ParsingError::Failed(s) => return Err(ParsedError::Failed(s)),
210                    }
211                }
212            }
213        }
214    }
215
216    #[tracing::instrument(skip(reader))]
217    fn clear_and_skip<R: Read, S: Skip<R>>(
218        &mut self,
219        reader: &mut R,
220        n: usize,
221    ) -> Result<(), ParsedError> {
222        tracing::debug!("ClearAndSkip");
223        if n <= self.buffer().len() {
224            tracing::debug!(n, "skip by set_position");
225            self.set_position(n);
226            return Ok(());
227        }
228
229        let skip_n = n - self.buffer().len();
230        tracing::debug!(skip_n, "clear and skip bytes");
231        self.clear();
232
233        let done = S::skip_by_seek(reader, skip_n.try_into().unwrap())?;
234        if !done {
235            tracing::debug!(skip_n, "skip by using our buffer");
236            let mut skipped = 0;
237            while skipped < skip_n {
238                let n = self.fill_buf(reader, skip_n - skipped)?;
239                skipped += n;
240                if skipped <= skip_n {
241                    self.clear();
242                } else {
243                    let remain = skipped - skip_n;
244                    self.set_position(self.buffer().len() - remain);
245                    break;
246                }
247            }
248        } else {
249            tracing::debug!(skip_n, "skip with seek");
250        }
251
252        if self.buffer().is_empty() {
253            self.fill_buf(reader, MIN_GROW_SIZE)?;
254        }
255        Ok(())
256    }
257}
258
259impl BufParser for MediaParser {
260    fn fill_buf<R: Read>(&mut self, reader: &mut R, size: usize) -> io::Result<usize> {
261        self.buf_mut().reserve_exact(size);
262
263        let n = reader.take(size as u64).read_to_end(self.buf_mut())?;
264        if n == 0 {
265            return Err(std::io::ErrorKind::UnexpectedEof.into());
266        }
267
268        Ok(n)
269    }
270}
271
272impl Buf for MediaParser {
273    fn buffer(&self) -> &[u8] {
274        &self.buf()[self.position..]
275    }
276
277    fn clear(&mut self) {
278        self.buf_mut().clear();
279    }
280
281    fn set_position(&mut self, pos: usize) {
282        self.position = pos;
283    }
284
285    fn position(&self) -> usize {
286        self.position
287    }
288}
289
290pub trait ParseOutput<R, S>: Sized {
291    fn parse(parser: &mut MediaParser, ms: MediaSource<R, S>) -> crate::Result<Self>;
292}
293
294impl<R: Read, S: Skip<R>> ParseOutput<R, S> for ExifIter {
295    fn parse(parser: &mut MediaParser, mut ms: MediaSource<R, S>) -> crate::Result<Self> {
296        if !ms.has_exif() {
297            return Err(crate::Error::ParseFailed("no Exif data here".into()));
298        }
299        parse_exif_iter::<R, S>(parser, ms.mime.unwrap_image(), &mut ms.reader)
300    }
301}
302
303impl<R: Read, S: Skip<R>> ParseOutput<R, S> for TrackInfo {
304    fn parse(parser: &mut MediaParser, mut ms: MediaSource<R, S>) -> crate::Result<Self> {
305        if !ms.has_track() {
306            return Err(crate::Error::ParseFailed("no track info here".into()));
307        }
308        let out = parser.load_and_parse::<R, S, _, _>(ms.reader.by_ref(), |data, _| {
309            parse_track_info(data, ms.mime.unwrap_video())
310                .map_err(|e| ParsingErrorState::new(e, None))
311        })?;
312        Ok(out)
313    }
314}
315
316/// A `MediaParser`/`AsyncMediaParser` can parse media info from a
317/// [`MediaSource`].
318///
319/// `MediaParser`/`AsyncMediaParser` manages inner parse buffers that can be
320/// shared between multiple parsing tasks, thus avoiding frequent memory
321/// allocations.
322///
323/// Therefore:
324///
325/// - Try to reuse a `MediaParser`/`AsyncMediaParser` instead of creating a new
326///   one every time you need it.
327///   
328/// - `MediaSource` should be created directly from `Read`, not from `BufRead`.
329///
330/// ## Example
331///
332/// ```rust
333/// use nom_exif::*;
334/// use chrono::DateTime;
335///
336/// let mut parser = MediaParser::new();
337///
338/// // ------------------- Parse Exif Info
339/// let ms = MediaSource::file_path("./testdata/exif.heic").unwrap();
340/// assert!(ms.has_exif());
341/// let mut iter: ExifIter = parser.parse(ms).unwrap();
342///
343/// let entry = iter.next().unwrap();
344/// assert_eq!(entry.tag().unwrap(), ExifTag::Make);
345/// assert_eq!(entry.get_value().unwrap().as_str().unwrap(), "Apple");
346///
347/// // Convert `ExifIter` into an `Exif`. Clone it before converting, so that
348/// // we can start the iteration from the beginning.
349/// let exif: Exif = iter.clone().into();
350/// assert_eq!(exif.get(ExifTag::Make).unwrap().as_str().unwrap(), "Apple");
351///
352/// // ------------------- Parse Track Info
353/// let ms = MediaSource::file_path("./testdata/meta.mov").unwrap();
354/// assert!(ms.has_track());
355/// let info: TrackInfo = parser.parse(ms).unwrap();
356///
357/// assert_eq!(info.get(TrackInfoTag::Make), Some(&"Apple".into()));
358/// assert_eq!(info.get(TrackInfoTag::Model), Some(&"iPhone X".into()));
359/// assert_eq!(info.get(TrackInfoTag::GpsIso6709), Some(&"+27.1281+100.2508+000.000/".into()));
360/// assert_eq!(info.get_gps_info().unwrap().latitude_ref, 'N');
361/// assert_eq!(
362///     info.get_gps_info().unwrap().latitude,
363///     [(27, 1), (7, 1), (68, 100)].into(),
364/// );
365/// ```
366pub struct MediaParser {
367    bb: Buffers,
368    buf: Option<Vec<u8>>,
369    position: usize,
370}
371
372impl Debug for MediaParser {
373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374        f.debug_struct("MediaParser")
375            .field("buffers", &self.bb)
376            .field("buf len", &self.buf.as_ref().map(|x| x.len()))
377            .field("position", &self.position)
378            .finish_non_exhaustive()
379    }
380}
381
382impl Default for MediaParser {
383    fn default() -> Self {
384        Self {
385            bb: Buffers::new(),
386            buf: None,
387            position: 0,
388        }
389    }
390}
391
392pub(crate) trait ShareBuf {
393    fn share_buf(&mut self, range: Range<usize>) -> PartialVec;
394}
395
396impl ShareBuf for MediaParser {
397    fn share_buf(&mut self, mut range: Range<usize>) -> PartialVec {
398        let buf = self.buf.take().unwrap();
399        let vec = self.bb.release_to_share(buf);
400        range.start += self.position;
401        range.end += self.position;
402        PartialVec::new(vec, range)
403    }
404}
405
406impl MediaParser {
407    pub fn new() -> Self {
408        Self::default()
409    }
410
411    /// `MediaParser`/`AsyncMediaParser` comes with its own buffer management,
412    /// so that buffers can be reused during multiple parsing processes to
413    /// avoid frequent memory allocations. Therefore, try to reuse a
414    /// `MediaParser` instead of creating a new one every time you need it.
415    ///     
416    /// **Note**:
417    ///
418    /// - For [`ExifIter`] as parse output, Please avoid holding the `ExifIter`
419    ///   object all the time and drop it immediately after use. Otherwise, the
420    ///   parsing buffer referenced by the `ExifIter` object will not be reused
421    ///   by [`MediaParser`], resulting in repeated memory allocation in the
422    ///   subsequent parsing process.
423    ///
424    ///   If you really need to retain some data, please take out the required
425    ///   Entry values ​​and save them, or convert the `ExifIter` into an
426    ///   [`crate::Exif`] object to retain all Entry values.
427    ///
428    /// - For [`TrackInfo`] as parse output, you don't need to worry about
429    ///   this, because `TrackInfo` dosn't reference the parsing buffer.
430    pub fn parse<R: Read, S, O: ParseOutput<R, S>>(
431        &mut self,
432        mut ms: MediaSource<R, S>,
433    ) -> crate::Result<O> {
434        self.reset();
435        self.acquire_buf();
436
437        self.buf_mut().append(&mut ms.buf);
438        let res = self.do_parse(ms);
439
440        self.reset();
441        res
442    }
443
444    fn do_parse<R: Read, S, O: ParseOutput<R, S>>(
445        &mut self,
446        mut ms: MediaSource<R, S>,
447    ) -> Result<O, crate::Error> {
448        self.fill_buf(&mut ms.reader, INIT_BUF_SIZE)?;
449        let res = ParseOutput::parse(self, ms)?;
450        Ok(res)
451    }
452
453    fn reset(&mut self) {
454        // Ensure buf has been released
455        if let Some(buf) = self.buf.take() {
456            self.bb.release(buf);
457        }
458
459        // Reset position
460        self.set_position(0);
461    }
462
463    pub(crate) fn buf(&self) -> &Vec<u8> {
464        match self.buf.as_ref() {
465            Some(b) => b,
466            None => panic!("no buf here"),
467        }
468    }
469
470    fn buf_mut(&mut self) -> &mut Vec<u8> {
471        match self.buf.as_mut() {
472            Some(b) => b,
473            None => panic!("no buf here"),
474        }
475    }
476
477    fn acquire_buf(&mut self) {
478        assert!(self.buf.is_none());
479        self.buf = Some(self.bb.acquire());
480    }
481}
482
483#[cfg(test)]
484mod tests {
485    use std::sync::{LazyLock, Mutex, MutexGuard};
486
487    use super::*;
488    use test_case::case;
489
490    enum TrackExif {
491        Track,
492        Exif,
493        NoData,
494        Invalid,
495    }
496    use TrackExif::*;
497
498    static PARSER: LazyLock<Mutex<MediaParser>> = LazyLock::new(|| Mutex::new(MediaParser::new()));
499    fn parser() -> MutexGuard<'static, MediaParser> {
500        PARSER.lock().unwrap()
501    }
502
503    #[case("3gp_640x360.3gp", Track)]
504    #[case("broken.jpg", Exif)]
505    #[case("compatible-brands-fail.heic", Invalid)]
506    #[case("compatible-brands-fail.mov", Invalid)]
507    #[case("compatible-brands.heic", NoData)]
508    #[case("compatible-brands.mov", NoData)]
509    #[case("embedded-in-heic.mov", Track)]
510    #[case("exif.heic", Exif)]
511    #[case("exif.jpg", Exif)]
512    #[case("fujifilm_x_t1_01.raf.meta", Exif)]
513    #[case("meta.mov", Track)]
514    #[case("meta.mp4", Track)]
515    #[case("mka.mka", Track)]
516    #[case("mkv_640x360.mkv", Track)]
517    #[case("exif-one-entry.heic", Exif)]
518    #[case("no-exif.jpg", NoData)]
519    #[case("tif.tif", Exif)]
520    #[case("ramdisk.img", Invalid)]
521    #[case("webm_480.webm", Track)]
522    fn parse_media(path: &str, te: TrackExif) {
523        let mut parser = parser();
524        let ms = MediaSource::file_path(Path::new("testdata").join(path));
525        match te {
526            Track => {
527                let ms = ms.unwrap();
528                // println!("path: {path} mime: {:?}", ms.mime);
529                assert!(ms.has_track());
530                let _: TrackInfo = parser.parse(ms).unwrap();
531            }
532            Exif => {
533                let ms = ms.unwrap();
534                // println!("path: {path} mime: {:?}", ms.mime);
535                assert!(ms.has_exif());
536                let mut it: ExifIter = parser.parse(ms).unwrap();
537                let _ = it.parse_gps_info();
538
539                if path.contains("one-entry") {
540                    assert!(it.next().is_some());
541                    assert!(it.next().is_none());
542
543                    let exif: crate::Exif = it.clone_and_rewind().into();
544                    assert!(exif.get(ExifTag::Orientation).is_some());
545                } else {
546                    let _: crate::Exif = it.clone_and_rewind().into();
547                }
548            }
549            NoData => {
550                let ms = ms.unwrap();
551                // println!("path: {path} mime: {:?}", ms.mime);
552                if ms.has_exif() {
553                    let res: Result<ExifIter, _> = parser.parse(ms);
554                    res.unwrap_err();
555                } else if ms.has_track() {
556                    let res: Result<TrackInfo, _> = parser.parse(ms);
557                    res.unwrap_err();
558                }
559            }
560            Invalid => {
561                ms.unwrap_err();
562            }
563        }
564    }
565
566    use crate::testkit::open_sample;
567    use crate::{EntryValue, ExifTag, TrackInfoTag};
568    use chrono::DateTime;
569    use test_case::test_case;
570
571    use crate::video::TrackInfoTag::*;
572
573    #[test_case("mkv_640x360.mkv", ImageWidth, 640_u32.into())]
574    #[test_case("mkv_640x360.mkv", ImageHeight, 360_u32.into())]
575    #[test_case("mkv_640x360.mkv", DurationMs, 13346_u64.into())]
576    #[test_case("mkv_640x360.mkv", CreateDate, DateTime::parse_from_str("2008-08-08T08:08:08Z", "%+").unwrap().into())]
577    #[test_case("meta.mov", Make, "Apple".into())]
578    #[test_case("meta.mov", Model, "iPhone X".into())]
579    #[test_case("meta.mov", GpsIso6709, "+27.1281+100.2508+000.000/".into())]
580    #[test_case("meta.mp4", ImageWidth, 1920_u32.into())]
581    #[test_case("meta.mp4", ImageHeight, 1080_u32.into())]
582    #[test_case("meta.mp4", DurationMs, 1063_u64.into())]
583    #[test_case("meta.mp4", GpsIso6709, "+27.2939+112.6932/".into())]
584    #[test_case("meta.mp4", CreateDate, DateTime::parse_from_str("2024-02-03T07:05:38Z", "%+").unwrap().into())]
585    fn parse_track_info(path: &str, tag: TrackInfoTag, v: EntryValue) {
586        let mut parser = parser();
587
588        let mf = MediaSource::file(open_sample(path).unwrap()).unwrap();
589        let info: TrackInfo = parser.parse(mf).unwrap();
590        assert_eq!(info.get(tag).unwrap(), &v);
591
592        let mf = MediaSource::unseekable(open_sample(path).unwrap()).unwrap();
593        let info: TrackInfo = parser.parse(mf).unwrap();
594        assert_eq!(info.get(tag).unwrap(), &v);
595    }
596}