1use std::{
2 cmp::{max, min},
3 fmt::{Debug, Display},
4 fs::File,
5 io::{self, Read, Seek},
6 path::Path,
7};
8
9use crate::{
10 error::{ParsedError, ParsingError, ParsingErrorState},
11 exif::TiffHeader,
12 file::MediaMime,
13 ExifIter, TrackInfo,
14};
15
16pub(crate) type SkipBySeekFn<R> = fn(&mut R, u64) -> io::Result<bool>;
25
26pub struct MediaSource<R> {
50 pub(crate) reader: R,
51 pub(crate) buf: Vec<u8>,
52 pub(crate) mime: MediaMime,
53 pub(crate) skip_by_seek: SkipBySeekFn<R>,
54 pub(crate) memory: Option<bytes::Bytes>,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum MediaKind {
68 Image,
69 Track,
70}
71
72impl<R> Debug for MediaSource<R> {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 f.debug_struct("MediaSource")
75 .field("mime", &self.mime)
76 .finish_non_exhaustive()
77 }
78}
79
80const HEADER_PARSE_BUF_SIZE: usize = 128;
82
83impl<R> MediaSource<R> {
84 pub fn kind(&self) -> MediaKind {
86 match self.mime {
87 MediaMime::Image(_) => MediaKind::Image,
88 MediaMime::Track(_) => MediaKind::Track,
89 }
90 }
91}
92
93impl<R: Read> MediaSource<R> {
94 fn build(mut reader: R, skip_by_seek: SkipBySeekFn<R>) -> crate::Result<Self> {
95 let mut buf = Vec::with_capacity(HEADER_PARSE_BUF_SIZE);
96 reader
97 .by_ref()
98 .take(HEADER_PARSE_BUF_SIZE as u64)
99 .read_to_end(&mut buf)?;
100 let mime: MediaMime = buf.as_slice().try_into()?;
101 Ok(Self {
102 reader,
103 buf,
104 mime,
105 skip_by_seek,
106 memory: None,
107 })
108 }
109
110 pub fn unseekable(reader: R) -> crate::Result<Self> {
116 Self::build(reader, |_, _| Ok(false))
117 }
118}
119
120impl<R: Read + Seek> MediaSource<R> {
121 pub fn seekable(reader: R) -> crate::Result<Self> {
126 Self::build(reader, |r, n| {
127 let signed: i64 = n
128 .try_into()
129 .map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?;
130 r.seek_relative(signed)?;
131 Ok(true)
132 })
133 }
134}
135
136impl MediaSource<File> {
137 pub fn open<P: AsRef<Path>>(path: P) -> crate::Result<Self> {
142 Self::seekable(File::open(path)?)
143 }
144}
145
146impl MediaSource<()> {
147 #[deprecated(
179 since = "3.3.0",
180 note = "Use `MediaSource::from_memory` and the unified `parse_*` \
181 methods (which now accept memory-mode sources directly). \
182 The `MediaSource<()>` shape will be removed in v4."
183 )]
184 pub fn from_bytes(bytes: impl Into<bytes::Bytes>) -> crate::Result<Self> {
185 let bytes = bytes.into();
186 let head_end = bytes.len().min(HEADER_PARSE_BUF_SIZE);
187 let mime: MediaMime = bytes[..head_end].try_into()?;
188 Ok(Self {
189 reader: (),
190 buf: Vec::new(),
191 mime,
192 skip_by_seek: |_, _| Ok(false),
195 memory: Some(bytes),
196 })
197 }
198
199 pub(crate) fn into_empty(self) -> MediaSource<std::io::Empty> {
205 MediaSource {
206 reader: std::io::empty(),
207 buf: self.buf,
208 mime: self.mime,
209 skip_by_seek: |_, _| Ok(false),
212 memory: self.memory,
213 }
214 }
215}
216
217impl MediaSource<std::io::Empty> {
218 pub fn from_memory(bytes: impl Into<bytes::Bytes>) -> crate::Result<Self> {
252 let bytes = bytes.into();
253 let head_end = bytes.len().min(HEADER_PARSE_BUF_SIZE);
254 let mime: MediaMime = bytes[..head_end].try_into()?;
255 Ok(Self {
256 reader: std::io::empty(),
257 buf: Vec::new(),
258 mime,
259 skip_by_seek: |_, _| Ok(false),
261 memory: Some(bytes),
262 })
263 }
264}
265
266pub(crate) const INIT_BUF_SIZE: usize = 8 * 1024;
286pub(crate) const MIN_GROW_SIZE: usize = 16 * 1024;
287pub(crate) const MAX_PARSE_BUF_SIZE: usize = 1024 * 1024 * 1024;
288const MAX_REUSE_BUF_SIZE: usize = 4 * 1024 * 1024;
289
290pub(crate) trait Buf {
291 fn buffer(&self) -> &[u8];
292 fn clear(&mut self);
293
294 fn set_position(&mut self, pos: usize);
295 #[allow(unused)]
296 fn position(&self) -> usize;
297}
298
299#[derive(Debug, Default)]
311pub(crate) struct BufferedParserState {
312 cached: Option<bytes::Bytes>,
313 buf: Option<Vec<u8>>,
314 memory: Option<bytes::Bytes>,
319 position: usize,
320}
321
322impl BufferedParserState {
323 pub(crate) fn new() -> Self {
324 Self::default()
325 }
326
327 pub(crate) fn reset(&mut self) {
328 self.buf = None;
331 self.memory = None;
332 self.position = 0;
333 }
334
335 pub(crate) fn set_memory(&mut self, bytes: bytes::Bytes) {
340 debug_assert!(
341 self.buf.is_none() && self.memory.is_none(),
342 "set_memory called on non-clean state"
343 );
344 self.memory = Some(bytes);
345 self.position = 0;
346 }
347
348 pub(crate) fn is_memory_mode(&self) -> bool {
349 self.memory.is_some()
350 }
351
352 pub(crate) fn acquire_buf(&mut self) {
353 if self.memory.is_some() {
354 return;
356 }
357 debug_assert!(self.buf.is_none());
358 let buf = match self.cached.take() {
359 Some(b) => match b.try_into_mut() {
360 Ok(bm) => {
361 let mut v = Vec::<u8>::from(bm);
362 v.clear();
363 if v.capacity() > MAX_REUSE_BUF_SIZE {
364 v.shrink_to(MAX_REUSE_BUF_SIZE);
365 }
366 v
367 }
368 Err(_still_shared) => Vec::with_capacity(INIT_BUF_SIZE),
369 },
370 None => Vec::with_capacity(INIT_BUF_SIZE),
371 };
372 self.buf = Some(buf);
373 }
374
375 pub(crate) fn buf(&self) -> &Vec<u8> {
376 self.buf.as_ref().expect("no buf here")
377 }
378
379 pub(crate) fn buf_mut(&mut self) -> &mut Vec<u8> {
380 self.buf.as_mut().expect("no buf here")
381 }
382
383 #[cfg(test)]
384 pub(crate) fn cached_ptr_for_test(&self) -> Option<*const u8> {
385 self.cached.as_ref().map(|b| b.as_ptr())
386 }
387
388 #[cfg(test)]
389 pub(crate) fn buf_is_none_for_test(&self) -> bool {
390 self.buf.is_none()
391 }
392}
393
394impl Buf for BufferedParserState {
395 fn buffer(&self) -> &[u8] {
396 if let Some(m) = &self.memory {
397 return &m[self.position..];
398 }
399 &self.buf()[self.position..]
400 }
401 fn clear(&mut self) {
402 if self.memory.is_some() {
407 return;
408 }
409 self.buf_mut().clear();
410 }
411 fn set_position(&mut self, pos: usize) {
412 self.position = pos;
413 }
414 fn position(&self) -> usize {
415 self.position
416 }
417}
418
419impl ShareBuf for BufferedParserState {
420 fn share_buf(&mut self) -> (bytes::Bytes, usize) {
421 if let Some(m) = self.memory.take() {
422 let position = self.position;
425 return (m, position);
426 }
427 let vec = self.buf.take().expect("no buf to share");
428 let bytes = bytes::Bytes::from(vec);
429 let position = self.position;
430 self.cached = Some(bytes.clone());
431 (bytes, position)
432 }
433}
434
435pub(crate) enum SkipPlan {
438 AdvanceOnly,
440 ClearAndSkip { extra: usize },
442}
443
444pub(crate) fn clear_and_skip_decide(buffer_len: usize, n: usize) -> SkipPlan {
445 if n <= buffer_len {
446 SkipPlan::AdvanceOnly
447 } else {
448 SkipPlan::ClearAndSkip {
449 extra: n - buffer_len,
450 }
451 }
452}
453
454pub(crate) fn check_fill_size(existing_len: usize, requested: usize) -> io::Result<()> {
455 if requested.saturating_add(existing_len) > MAX_PARSE_BUF_SIZE {
456 tracing::error!(?requested, "the requested buffer size is too big");
457 return Err(io::ErrorKind::Unsupported.into());
458 }
459 Ok(())
460}
461
462pub(crate) enum LoopAction<O> {
463 Done(O),
465 NeedFill(usize),
467 Skip(usize),
469 Failed {
472 kind: crate::error::MalformedKind,
473 message: String,
474 },
475}
476
477pub(crate) type ParseFn<'a, O> =
479 dyn FnMut(&[u8], usize, Option<ParsingState>) -> Result<O, ParsingErrorState> + 'a;
480
481pub(crate) fn parse_loop_step<O>(
483 buffer: &[u8],
484 offset: usize,
485 parsing_state: &mut Option<ParsingState>,
486 parse: &mut ParseFn<'_, O>,
487) -> LoopAction<O> {
488 match parse(buffer, offset, parsing_state.take()) {
489 Ok(o) => LoopAction::Done(o),
490 Err(es) => {
491 *parsing_state = es.state;
492 match es.err {
493 ParsingError::Need(n) => LoopAction::NeedFill(n),
494 ParsingError::ClearAndSkip(n) => LoopAction::Skip(n),
495 ParsingError::Failed { kind, message } => LoopAction::Failed { kind, message },
496 }
497 }
498 }
499}
500
501#[derive(Debug, Clone)]
502pub(crate) enum ParsingState {
503 TiffHeader(TiffHeader),
504 HeifExifSize(usize),
505 Cr3ExifSize(usize),
506 PngPastSignature,
510 WebpPastHeader(usize),
515}
516
517impl Display for ParsingState {
518 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
519 match self {
520 ParsingState::TiffHeader(h) => Display::fmt(&format!("ParsingState: {h:?})"), f),
521 ParsingState::HeifExifSize(n) => Display::fmt(&format!("ParsingState: {n}"), f),
522 ParsingState::Cr3ExifSize(n) => Display::fmt(&format!("ParsingState: {n}"), f),
523 ParsingState::PngPastSignature => f.write_str("ParsingState: PngPastSignature"),
524 ParsingState::WebpPastHeader(n) => {
525 Display::fmt(&format!("ParsingState: WebpPastHeader({n})"), f)
526 }
527 }
528 }
529}
530
531pub(crate) trait BufParser: Buf + Debug {
534 fn fill_buf<R: Read>(&mut self, reader: &mut R, size: usize) -> io::Result<usize>;
535
536 fn load_and_parse<R: Read, P, O>(
537 &mut self,
538 reader: &mut R,
539 skip_by_seek: SkipBySeekFn<R>,
540 mut parse: P,
541 ) -> Result<O, ParsedError>
542 where
543 P: FnMut(&[u8], Option<ParsingState>) -> Result<O, ParsingErrorState>,
544 {
545 self.load_and_parse_with_offset(
546 reader,
547 skip_by_seek,
548 |data, _, state| parse(data, state),
549 0,
550 )
551 }
552
553 #[tracing::instrument(skip_all)]
554 fn load_and_parse_with_offset<R: Read, P, O>(
555 &mut self,
556 reader: &mut R,
557 skip_by_seek: SkipBySeekFn<R>,
558 mut parse: P,
559 offset: usize,
560 ) -> Result<O, ParsedError>
561 where
562 P: FnMut(&[u8], usize, Option<ParsingState>) -> Result<O, ParsingErrorState>,
563 {
564 if offset >= self.buffer().len() {
565 self.fill_buf(reader, MIN_GROW_SIZE)?;
566 }
567 let mut parsing_state: Option<ParsingState> = None;
568 loop {
569 match parse_loop_step(self.buffer(), offset, &mut parsing_state, &mut parse) {
570 LoopAction::Done(o) => return Ok(o),
571 LoopAction::NeedFill(needed) => {
572 let to_read = max(needed, MIN_GROW_SIZE);
573 let n = self.fill_buf(reader, to_read)?;
574 if n == 0 {
575 return Err(ParsedError::NoEnoughBytes);
576 }
577 }
578 LoopAction::Skip(n) => {
579 self.clear_and_skip(reader, skip_by_seek, n)?;
580 }
581 LoopAction::Failed { kind, message } => {
582 return Err(ParsedError::Failed { kind, message })
583 }
584 }
585 }
586 }
587
588 #[tracing::instrument(skip(reader, skip_by_seek))]
589 fn clear_and_skip<R: Read>(
590 &mut self,
591 reader: &mut R,
592 skip_by_seek: SkipBySeekFn<R>,
593 n: usize,
594 ) -> Result<(), ParsedError> {
595 match clear_and_skip_decide(self.buffer().len(), n) {
596 SkipPlan::AdvanceOnly => {
597 self.set_position(self.position() + n);
598 Ok(())
599 }
600 SkipPlan::ClearAndSkip { extra: skip_n } => {
601 self.clear();
602 let done = (skip_by_seek)(
603 reader,
604 skip_n.try_into().map_err(|_| ParsedError::Failed {
605 kind: crate::error::MalformedKind::IsoBmffBox,
609 message: "skip too many bytes".into(),
610 })?,
611 )?;
612 if !done {
613 let mut skipped = 0;
614 while skipped < skip_n {
615 let mut to_skip = skip_n - skipped;
616 to_skip = min(to_skip, MAX_PARSE_BUF_SIZE);
617 let n = self.fill_buf(reader, to_skip)?;
618 skipped += n;
619 if skipped <= skip_n {
620 self.clear();
621 } else {
622 let remain = skipped - skip_n;
623 self.set_position(self.buffer().len() - remain);
624 break;
625 }
626 }
627 }
628
629 if self.buffer().is_empty() {
630 self.fill_buf(reader, MIN_GROW_SIZE)?;
631 }
632 Ok(())
633 }
634 }
635 }
636}
637
638impl BufParser for MediaParser {
639 #[tracing::instrument(skip(self, reader), fields(buf_len=self.state.buffer().len()))]
640 fn fill_buf<R: Read>(&mut self, reader: &mut R, size: usize) -> io::Result<usize> {
641 if self.state.is_memory_mode() {
642 return Err(std::io::ErrorKind::UnexpectedEof.into());
646 }
647 check_fill_size(self.state.buf().len(), size)?;
648
649 let n = reader.take(size as u64).read_to_end(self.state.buf_mut())?;
655 if n == 0 {
656 tracing::error!(buf_len = self.state.buf().len(), "fill_buf: EOF");
657 return Err(std::io::ErrorKind::UnexpectedEof.into());
658 }
659
660 tracing::debug!(
661 ?size,
662 ?n,
663 buf_len = self.state.buf().len(),
664 "fill_buf: read bytes"
665 );
666
667 Ok(n)
668 }
669}
670
671impl Buf for MediaParser {
672 fn buffer(&self) -> &[u8] {
673 self.state.buffer()
674 }
675
676 fn clear(&mut self) {
677 self.state.clear();
678 }
679
680 fn set_position(&mut self, pos: usize) {
681 self.state.set_position(pos);
682 }
683
684 fn position(&self) -> usize {
685 self.state.position()
686 }
687}
688
689pub struct MediaParser {
738 state: BufferedParserState,
739}
740
741impl Debug for MediaParser {
742 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
743 f.debug_struct("MediaParser")
744 .field("state", &self.state)
745 .finish_non_exhaustive()
746 }
747}
748
749impl Default for MediaParser {
750 fn default() -> Self {
751 Self {
752 state: BufferedParserState::new(),
753 }
754 }
755}
756
757pub(crate) trait ShareBuf {
758 fn share_buf(&mut self) -> (bytes::Bytes, usize);
763}
764
765impl ShareBuf for MediaParser {
766 fn share_buf(&mut self) -> (bytes::Bytes, usize) {
767 self.state.share_buf()
768 }
769}
770
771impl MediaParser {
772 pub fn new() -> Self {
773 Self::default()
774 }
775
776 pub fn parse_exif<R: Read>(&mut self, mut ms: MediaSource<R>) -> crate::Result<ExifIter> {
788 self.reset();
789 let res: crate::Result<ExifIter> = (|| {
790 if let Some(memory) = ms.memory.take() {
791 self.state.set_memory(memory);
793 if !matches!(ms.mime, crate::file::MediaMime::Image(_)) {
794 return Err(crate::Error::ExifNotFound);
795 }
796 crate::exif::parse_exif_iter(
797 self,
798 ms.mime.unwrap_image(),
799 &mut ms.reader,
800 ms.skip_by_seek,
801 )
802 } else {
803 self.acquire_buf();
805 self.buf_mut().append(&mut ms.buf);
806 let is_png = matches!(
813 ms.mime,
814 crate::file::MediaMime::Image(
815 crate::file::MediaMimeImage::Png | crate::file::MediaMimeImage::Webp
816 )
817 );
818 match self.fill_buf(&mut ms.reader, INIT_BUF_SIZE) {
819 Ok(_) => {}
820 Err(e)
821 if is_png
822 && !self.buffer().is_empty()
823 && e.kind() == io::ErrorKind::UnexpectedEof => {}
824 Err(e) => return Err(e.into()),
825 }
826 if !matches!(ms.mime, crate::file::MediaMime::Image(_)) {
827 return Err(crate::Error::ExifNotFound);
828 }
829 crate::exif::parse_exif_iter(
830 self,
831 ms.mime.unwrap_image(),
832 &mut ms.reader,
833 ms.skip_by_seek,
834 )
835 }
836 })();
837 self.reset();
838 res
839 }
840
841 pub fn parse_track<R: Read>(&mut self, mut ms: MediaSource<R>) -> crate::Result<TrackInfo> {
851 self.reset();
852 let res: crate::Result<TrackInfo> = (|| {
853 if let Some(memory) = ms.memory.take() {
854 self.state.set_memory(memory);
856 let mime_track = match ms.mime {
857 crate::file::MediaMime::Image(_) => return Err(crate::Error::TrackNotFound),
858 crate::file::MediaMime::Track(t) => t,
859 };
860 let out = self.load_and_parse(&mut ms.reader, ms.skip_by_seek, |data, _| {
861 crate::video::parse_track_info(data, mime_track)
862 .map_err(|e| ParsingErrorState::new(e, None))
863 })?;
864 Ok(out)
865 } else {
866 self.acquire_buf();
868 self.buf_mut().append(&mut ms.buf);
869 self.fill_buf(&mut ms.reader, INIT_BUF_SIZE)?;
870 match ms.mime {
871 crate::file::MediaMime::Image(crate::file::MediaMimeImage::Jpeg) => {
872 self.parse_jpeg_motion_photo(&mut ms.reader)
873 }
874 crate::file::MediaMime::Image(_) => Err(crate::Error::TrackNotFound),
875 crate::file::MediaMime::Track(mime_track) => {
876 let skip = ms.skip_by_seek;
877 Ok(self.load_and_parse(ms.reader.by_ref(), skip, |data, _| {
878 crate::video::parse_track_info(data, mime_track)
879 .map_err(|e| ParsingErrorState::new(e, None))
880 })?)
881 }
882 }
883 }
884 })();
885 self.reset();
886 res
887 }
888
889 fn parse_jpeg_motion_photo<R: Read>(&mut self, reader: &mut R) -> crate::Result<TrackInfo> {
894 reader.read_to_end(self.buf_mut())?;
897 let buf = self.buf_mut();
898 let Some(offset) = crate::jpeg::find_motion_photo_offset(buf) else {
899 return Err(crate::Error::TrackNotFound);
900 };
901 let trailer_start = (buf.len() as u64)
902 .checked_sub(offset)
903 .ok_or(crate::Error::TrackNotFound)? as usize;
904 let trailer = &buf[trailer_start..];
905
906 let trailer_mime =
909 crate::file::MediaMime::try_from(trailer).map_err(|_| crate::Error::TrackNotFound)?;
910 let mime_track = match trailer_mime {
911 crate::file::MediaMime::Track(t) => t,
912 crate::file::MediaMime::Image(_) => return Err(crate::Error::TrackNotFound),
913 };
914 crate::video::parse_track_info(trailer, mime_track).map_err(|e| match e {
915 crate::error::ParsingError::Need(_) | crate::error::ParsingError::ClearAndSkip(_) => {
916 crate::Error::UnexpectedEof {
917 context: "motion-photo trailer",
918 }
919 }
920 crate::error::ParsingError::Failed { kind, message } => {
921 crate::Error::Malformed { kind, message }
922 }
923 })
924 }
925
926 #[deprecated(
932 since = "3.3.0",
933 note = "Use `parse_exif` directly — it now accepts memory-mode \
934 sources built via `MediaSource::from_memory`."
935 )]
936 pub fn parse_exif_from_bytes(&mut self, ms: MediaSource<()>) -> crate::Result<ExifIter> {
937 self.parse_exif(ms.into_empty())
938 }
939
940 #[deprecated(
943 since = "3.3.0",
944 note = "Use `parse_track` with `MediaSource::from_memory`."
945 )]
946 pub fn parse_track_from_bytes(&mut self, ms: MediaSource<()>) -> crate::Result<TrackInfo> {
947 self.parse_track(ms.into_empty())
948 }
949
950 pub fn parse_image_metadata<R: Read>(
962 &mut self,
963 mut ms: MediaSource<R>,
964 ) -> crate::Result<crate::image_metadata::ImageMetadata<crate::ExifIter>> {
965 self.reset();
966 let res: crate::Result<crate::image_metadata::ImageMetadata<crate::ExifIter>> = (|| {
967 let mime_img = match ms.mime {
969 crate::file::MediaMime::Image(img) => img,
970 crate::file::MediaMime::Track(_) => return Err(crate::Error::ExifNotFound),
971 };
972
973 if let Some(memory) = ms.memory.take() {
975 self.state.set_memory(memory);
976 } else {
977 self.acquire_buf();
978 self.buf_mut().append(&mut ms.buf);
979 let is_png = matches!(
986 mime_img,
987 crate::file::MediaMimeImage::Png | crate::file::MediaMimeImage::Webp
988 );
989 match self.fill_buf(&mut ms.reader, INIT_BUF_SIZE) {
990 Ok(_) => {}
991 Err(e)
992 if is_png
993 && !self.buffer().is_empty()
994 && e.kind() == io::ErrorKind::UnexpectedEof => {}
995 Err(e) => return Err(e.into()),
996 }
997 }
998
999 if mime_img == crate::file::MediaMimeImage::Png {
1000 let (exif, text_chunks) =
1001 crate::exif::parse_png_full(self, &mut ms.reader, ms.skip_by_seek)?;
1002 let format = if text_chunks.is_empty() {
1003 None
1004 } else {
1005 Some(crate::ImageFormatMetadata::Png(crate::PngTextChunks {
1006 entries: text_chunks,
1007 }))
1008 };
1009 if exif.is_none() && format.is_none() {
1010 return Err(crate::Error::ExifNotFound);
1011 }
1012 Ok(crate::ImageMetadata { exif, format })
1013 } else {
1014 let iter =
1016 crate::exif::parse_exif_iter(self, mime_img, &mut ms.reader, ms.skip_by_seek)?;
1017 Ok(crate::ImageMetadata {
1018 exif: Some(iter),
1019 format: None,
1020 })
1021 }
1022 })(
1023 );
1024 self.reset();
1025 res
1026 }
1027
1028 fn reset(&mut self) {
1029 self.state.reset();
1030 }
1031
1032 fn buf_mut(&mut self) -> &mut Vec<u8> {
1033 self.state.buf_mut()
1034 }
1035
1036 fn acquire_buf(&mut self) {
1037 self.state.acquire_buf();
1038 }
1039}
1040
1041#[cfg(feature = "tokio")]
1042mod tokio_impl {
1043 use super::*;
1044 use crate::error::ParsingErrorState;
1045 use crate::parser_async::{AsyncBufParser, AsyncMediaSource};
1046 use tokio::io::{AsyncRead, AsyncReadExt};
1047
1048 impl AsyncBufParser for MediaParser {
1049 async fn fill_buf<R: AsyncRead + Unpin>(
1050 &mut self,
1051 reader: &mut R,
1052 size: usize,
1053 ) -> std::io::Result<usize> {
1054 if self.state.is_memory_mode() {
1055 return Err(std::io::ErrorKind::UnexpectedEof.into());
1059 }
1060 check_fill_size(self.state.buf().len(), size)?;
1061 let n = reader
1063 .take(size as u64)
1064 .read_to_end(self.state.buf_mut())
1065 .await?;
1066 if n == 0 {
1067 return Err(std::io::ErrorKind::UnexpectedEof.into());
1068 }
1069 Ok(n)
1070 }
1071 }
1072
1073 impl MediaParser {
1074 pub async fn parse_exif_async<R: AsyncRead + Unpin + Send>(
1081 &mut self,
1082 mut ms: AsyncMediaSource<R>,
1083 ) -> crate::Result<ExifIter> {
1084 self.reset();
1085 let res: crate::Result<ExifIter> = async {
1086 if let Some(memory) = ms.memory.take() {
1087 self.state.set_memory(memory);
1089 if !matches!(ms.mime, crate::file::MediaMime::Image(_)) {
1090 return Err(crate::Error::ExifNotFound);
1091 }
1092 crate::exif::parse_exif_iter_async(
1093 self,
1094 ms.mime.unwrap_image(),
1095 &mut ms.reader,
1096 ms.skip_by_seek,
1097 )
1098 .await
1099 } else {
1100 self.acquire_buf();
1101 self.buf_mut().append(&mut ms.buf);
1102 let is_png = matches!(
1108 ms.mime,
1109 crate::file::MediaMime::Image(
1110 crate::file::MediaMimeImage::Png | crate::file::MediaMimeImage::Webp
1111 )
1112 );
1113 match <Self as AsyncBufParser>::fill_buf(self, &mut ms.reader, INIT_BUF_SIZE)
1114 .await
1115 {
1116 Ok(_) => {}
1117 Err(e)
1118 if is_png
1119 && !self.buffer().is_empty()
1120 && e.kind() == io::ErrorKind::UnexpectedEof => {}
1121 Err(e) => return Err(e.into()),
1122 }
1123 if !matches!(ms.mime, crate::file::MediaMime::Image(_)) {
1124 return Err(crate::Error::ExifNotFound);
1125 }
1126 crate::exif::parse_exif_iter_async(
1127 self,
1128 ms.mime.unwrap_image(),
1129 &mut ms.reader,
1130 ms.skip_by_seek,
1131 )
1132 .await
1133 }
1134 }
1135 .await;
1136 self.reset();
1137 res
1138 }
1139
1140 pub async fn parse_image_metadata_async<R: AsyncRead + Unpin + Send>(
1141 &mut self,
1142 mut ms: AsyncMediaSource<R>,
1143 ) -> crate::Result<crate::image_metadata::ImageMetadata<crate::ExifIter>> {
1144 self.reset();
1145 let res: crate::Result<crate::image_metadata::ImageMetadata<crate::ExifIter>> = async {
1146 let mime_img = match ms.mime {
1147 crate::file::MediaMime::Image(img) => img,
1148 crate::file::MediaMime::Track(_) => return Err(crate::Error::ExifNotFound),
1149 };
1150
1151 if let Some(memory) = ms.memory.take() {
1152 self.state.set_memory(memory);
1153 } else {
1154 self.acquire_buf();
1155 self.buf_mut().append(&mut ms.buf);
1156 let is_png = matches!(
1162 mime_img,
1163 crate::file::MediaMimeImage::Png | crate::file::MediaMimeImage::Webp
1164 );
1165 match <Self as AsyncBufParser>::fill_buf(self, &mut ms.reader, INIT_BUF_SIZE)
1166 .await
1167 {
1168 Ok(_) => {}
1169 Err(e)
1170 if is_png
1171 && !self.buffer().is_empty()
1172 && e.kind() == io::ErrorKind::UnexpectedEof => {}
1173 Err(e) => return Err(e.into()),
1174 }
1175 }
1176
1177 if mime_img == crate::file::MediaMimeImage::Png {
1178 let (exif, text_chunks) =
1179 crate::exif::parse_png_full_async(self, &mut ms.reader, ms.skip_by_seek)
1180 .await?;
1181 let format = if text_chunks.is_empty() {
1182 None
1183 } else {
1184 Some(crate::ImageFormatMetadata::Png(crate::PngTextChunks {
1185 entries: text_chunks,
1186 }))
1187 };
1188 if exif.is_none() && format.is_none() {
1189 return Err(crate::Error::ExifNotFound);
1190 }
1191 Ok(crate::image_metadata::ImageMetadata { exif, format })
1192 } else {
1193 let iter = crate::exif::parse_exif_iter_async(
1194 self,
1195 mime_img,
1196 &mut ms.reader,
1197 ms.skip_by_seek,
1198 )
1199 .await?;
1200 Ok(crate::image_metadata::ImageMetadata {
1201 exif: Some(iter),
1202 format: None,
1203 })
1204 }
1205 }
1206 .await;
1207 self.reset();
1208 res
1209 }
1210
1211 pub async fn parse_track_async<R: AsyncRead + Unpin + Send>(
1217 &mut self,
1218 mut ms: AsyncMediaSource<R>,
1219 ) -> crate::Result<TrackInfo> {
1220 self.reset();
1221 let res: crate::Result<TrackInfo> = async {
1222 let mime_track = match ms.mime {
1223 crate::file::MediaMime::Image(_) => return Err(crate::Error::TrackNotFound),
1224 crate::file::MediaMime::Track(t) => t,
1225 };
1226 if let Some(memory) = ms.memory.take() {
1227 self.state.set_memory(memory);
1228 } else {
1229 self.acquire_buf();
1230 self.buf_mut().append(&mut ms.buf);
1231 <Self as AsyncBufParser>::fill_buf(self, &mut ms.reader, INIT_BUF_SIZE).await?;
1232 }
1233 let skip = ms.skip_by_seek;
1234 let out = <Self as AsyncBufParser>::load_and_parse(
1235 self,
1236 &mut ms.reader,
1237 skip,
1238 |data, _| {
1239 crate::video::parse_track_info(data, mime_track)
1240 .map_err(|e| ParsingErrorState::new(e, None))
1241 },
1242 )
1243 .await?;
1244 Ok(out)
1245 }
1246 .await;
1247 self.reset();
1248 res
1249 }
1250 }
1251}
1252
1253#[cfg(test)]
1254mod tests {
1255 use std::sync::{LazyLock, Mutex, MutexGuard};
1256
1257 use super::*;
1258 use test_case::case;
1259
1260 enum TrackExif {
1261 Track,
1262 Exif,
1263 NoData,
1264 Invalid,
1265 }
1266 use TrackExif::*;
1267
1268 static PARSER: LazyLock<Mutex<MediaParser>> = LazyLock::new(|| Mutex::new(MediaParser::new()));
1269 fn parser() -> MutexGuard<'static, MediaParser> {
1270 PARSER.lock().unwrap()
1271 }
1272
1273 #[case("3gp_640x360.3gp", Track)]
1274 #[case("broken.jpg", Exif)]
1275 #[case("compatible-brands-fail.heic", Invalid)]
1276 #[case("compatible-brands-fail.mov", Invalid)]
1277 #[case("compatible-brands.heic", NoData)]
1278 #[case("compatible-brands.mov", NoData)]
1279 #[case("embedded-in-heic.mov", Track)]
1280 #[case("exif.heic", Exif)]
1281 #[case("exif.jpg", Exif)]
1282 #[case("exif-no-tz.jpg", Exif)]
1283 #[case("fujifilm_x_t1_01.raf.meta", Exif)]
1284 #[case("meta.mov", Track)]
1285 #[case("meta.mp4", Track)]
1286 #[case("mka.mka", Track)]
1287 #[case("mkv_640x360.mkv", Track)]
1288 #[case("exif-one-entry.heic", Exif)]
1289 #[case("no-exif.jpg", NoData)]
1290 #[case("tif.tif", Exif)]
1291 #[case("ramdisk.img", Invalid)]
1292 #[case("webm_480.webm", Track)]
1293 fn parse_media(path: &str, te: TrackExif) {
1294 let mut parser = parser();
1295 let ms = MediaSource::open(Path::new("testdata").join(path));
1296 match te {
1297 Track => {
1298 let ms = ms.unwrap();
1299 assert_eq!(ms.kind(), MediaKind::Track);
1300 let _: TrackInfo = parser.parse_track(ms).unwrap();
1301 }
1302 Exif => {
1303 let ms = ms.unwrap();
1304 assert_eq!(ms.kind(), MediaKind::Image);
1305 let mut it: ExifIter = parser.parse_exif(ms).unwrap();
1306 let _ = it.parse_gps();
1307
1308 if path.contains("one-entry") {
1309 assert!(it.next().is_some());
1310 assert!(it.next().is_none());
1311
1312 let exif: crate::Exif = it.clone_rewound().into();
1313 assert!(exif.get(ExifTag::Orientation).is_some());
1314 } else {
1315 let _: crate::Exif = it.clone_rewound().into();
1316 }
1317 }
1318 NoData => {
1319 let ms = ms.unwrap();
1320 match ms.kind() {
1321 MediaKind::Image => {
1322 let res = parser.parse_exif(ms);
1323 res.unwrap_err();
1324 }
1325 MediaKind::Track => {
1326 let res = parser.parse_track(ms);
1327 res.unwrap_err();
1328 }
1329 }
1330 }
1331 Invalid => {
1332 ms.unwrap_err();
1333 }
1334 }
1335 }
1336
1337 use crate::testkit::open_sample;
1338 use crate::{EntryValue, Exif, ExifTag, TrackInfoTag};
1339 use chrono::{DateTime, FixedOffset, NaiveDateTime};
1340 use test_case::test_case;
1341
1342 #[test_case("exif.jpg", ExifTag::DateTimeOriginal, DateTime::parse_from_str("2023-07-09T20:36:33+08:00", "%+").unwrap().into())]
1343 #[test_case("exif.heic", ExifTag::DateTimeOriginal, DateTime::parse_from_str("2022-07-22T21:26:32+08:00", "%+").unwrap().into())]
1344 #[test_case("exif.jpg", ExifTag::DateTimeOriginal,
1345 (NaiveDateTime::parse_from_str("2023-07-09T20:36:33", "%Y-%m-%dT%H:%M:%S").unwrap(),
1346 Some(FixedOffset::east_opt(8*3600).unwrap())).into())]
1347 #[test_case("exif-no-tz.jpg", ExifTag::DateTimeOriginal,
1348 (NaiveDateTime::parse_from_str("2023-07-09T20:36:33", "%Y-%m-%dT%H:%M:%S").unwrap(), None).into())]
1349 fn parse_exif(path: &str, tag: ExifTag, v: EntryValue) {
1350 let mut parser = parser();
1351
1352 let mf = MediaSource::seekable(open_sample(path).unwrap()).unwrap();
1353 assert_eq!(mf.kind(), MediaKind::Image);
1354 let iter: ExifIter = parser.parse_exif(mf).unwrap();
1355 let exif: Exif = iter.into();
1356 assert_eq!(exif.get(tag).unwrap(), &v);
1357
1358 let mf = MediaSource::unseekable(open_sample(path).unwrap()).unwrap();
1359 assert_eq!(mf.kind(), MediaKind::Image);
1360 let iter: ExifIter = parser.parse_exif(mf).unwrap();
1361 let exif: Exif = iter.into();
1362 assert_eq!(exif.get(tag).unwrap(), &v);
1363 }
1364
1365 use crate::video::TrackInfoTag::*;
1366
1367 #[test_case("mkv_640x360.mkv", Width, 640_u32.into())]
1368 #[test_case("mkv_640x360.mkv", Height, 360_u32.into())]
1369 #[test_case("mkv_640x360.mkv", DurationMs, 13346_u64.into())]
1370 #[test_case("mkv_640x360.mkv", CreateDate, DateTime::parse_from_str("2008-08-08T08:08:08Z", "%+").unwrap().into())]
1371 #[test_case("meta.mov", Make, "Apple".into())]
1372 #[test_case("meta.mov", Model, "iPhone X".into())]
1373 #[test_case("meta.mov", GpsIso6709, "+27.1281+100.2508+000.000/".into())]
1374 #[test_case("meta.mov", CreateDate, DateTime::parse_from_str("2019-02-12T15:27:12+08:00", "%+").unwrap().into())]
1375 #[test_case("meta.mp4", Width, 1920_u32.into())]
1376 #[test_case("meta.mp4", Height, 1080_u32.into())]
1377 #[test_case("meta.mp4", DurationMs, 1063_u64.into())]
1378 #[test_case("meta.mp4", GpsIso6709, "+27.2939+112.6932/".into())]
1379 #[test_case("meta.mp4", CreateDate, DateTime::parse_from_str("2024-02-03T07:05:38Z", "%+").unwrap().into())]
1380 #[test_case("udta.auth.mp4", Author, "ReplayKitRecording".into(); "udta author")]
1381 #[test_case("auth.mov", Author, "ReplayKitRecording".into(); "mov author")]
1382 #[test_case("sony-a7-xavc.MP4", Width, 1920_u32.into())]
1383 #[test_case("sony-a7-xavc.MP4", Height, 1080_u32.into())]
1384 #[test_case("sony-a7-xavc.MP4", DurationMs, 1440_u64.into())]
1385 #[test_case("sony-a7-xavc.MP4", CreateDate, DateTime::parse_from_str("2026-04-26T09:25:15+00:00", "%+").unwrap().into())]
1386 fn parse_track_info(path: &str, tag: TrackInfoTag, v: EntryValue) {
1387 let mut parser = parser();
1388
1389 let mf = MediaSource::seekable(open_sample(path).unwrap()).unwrap();
1390 let info: TrackInfo = parser.parse_track(mf).unwrap();
1391 assert_eq!(info.get(tag).unwrap(), &v);
1392
1393 let mf = MediaSource::unseekable(open_sample(path).unwrap()).unwrap();
1394 let info: TrackInfo = parser.parse_track(mf).unwrap();
1395 assert_eq!(info.get(tag).unwrap(), &v);
1396 }
1397
1398 #[test_case("crash_moov-trak")]
1399 #[test_case("crash_skip_large")]
1400 #[test_case("crash_add_large")]
1401 fn parse_track_crash(path: &str) {
1402 let mut parser = parser();
1403
1404 let mf = MediaSource::seekable(open_sample(path).unwrap()).unwrap();
1405 let _: TrackInfo = parser.parse_track(mf).unwrap_or_default();
1406
1407 let mf = MediaSource::unseekable(open_sample(path).unwrap()).unwrap();
1408 let _: TrackInfo = parser.parse_track(mf).unwrap_or_default();
1409 }
1410
1411 #[test]
1416 fn parse_oom_large_box() {
1417 let mut parser = parser();
1418
1419 let mf = MediaSource::seekable(open_sample("oom_large_box.heic").unwrap()).unwrap();
1420 let _: Result<ExifIter, _> = parser.parse_exif(mf);
1421
1422 let mf = MediaSource::unseekable(open_sample("oom_large_box.heic").unwrap()).unwrap();
1423 let _: Result<ExifIter, _> = parser.parse_exif(mf);
1424
1425 let mf = MediaSource::seekable(open_sample("oom_large_box.heic").unwrap()).unwrap();
1426 let _: TrackInfo = parser.parse_track(mf).unwrap_or_default();
1427
1428 let mf = MediaSource::unseekable(open_sample("oom_large_box.heic").unwrap()).unwrap();
1429 let _: TrackInfo = parser.parse_track(mf).unwrap_or_default();
1430 }
1431
1432 #[test]
1433 fn media_kind_classifies_image_and_track() {
1434 let img = MediaSource::open("testdata/exif.jpg").unwrap();
1435 assert_eq!(img.kind(), MediaKind::Image);
1436
1437 let trk = MediaSource::open("testdata/meta.mov").unwrap();
1438 assert_eq!(trk.kind(), MediaKind::Track);
1439 }
1440
1441 #[test]
1442 fn media_source_open() {
1443 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1444 assert_eq!(ms.kind(), MediaKind::Image);
1445 }
1446
1447 #[test]
1448 fn parse_exif_returns_exif_iter() {
1449 let mut parser = parser();
1450 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1451 let _: ExifIter = parser.parse_exif(ms).unwrap();
1452 }
1453
1454 #[test]
1455 fn parse_track_returns_track_info() {
1456 let mut parser = parser();
1457 let ms = MediaSource::open("testdata/meta.mov").unwrap();
1458 let _: TrackInfo = parser.parse_track(ms).unwrap();
1459 }
1460
1461 #[test]
1462 fn parse_exif_on_track_returns_exif_not_found_v3() {
1463 let mut parser = parser();
1464 let ms = MediaSource::open("testdata/meta.mov").unwrap();
1465 let res = parser.parse_exif(ms);
1466 assert!(matches!(res, Err(crate::Error::ExifNotFound)));
1467 }
1468
1469 #[test]
1470 fn parse_track_on_image_returns_track_not_found_v3() {
1471 let mut parser = parser();
1472 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1473 let res = parser.parse_track(ms);
1474 assert!(matches!(res, Err(crate::Error::TrackNotFound)));
1475 }
1476
1477 #[cfg(feature = "tokio-fs")]
1478 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1479 async fn media_parser_parse_exif_async() {
1480 use crate::parser_async::AsyncMediaSource;
1481 let mut parser = MediaParser::new();
1482 let ms = AsyncMediaSource::open("testdata/exif.jpg").await.unwrap();
1483 let _: ExifIter = parser.parse_exif_async(ms).await.unwrap();
1484 }
1485
1486 #[cfg(feature = "tokio-fs")]
1487 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1488 async fn media_parser_parse_track_async() {
1489 use crate::parser_async::AsyncMediaSource;
1490 let mut parser = MediaParser::new();
1491 let ms = AsyncMediaSource::open("testdata/meta.mov").await.unwrap();
1492 let _: TrackInfo = parser.parse_track_async(ms).await.unwrap();
1493 }
1494
1495 #[test]
1496 fn parser_recycles_alloc_when_exif_iter_dropped() {
1497 let mut parser = MediaParser::new();
1498
1499 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1500 let iter = parser.parse_exif(ms).unwrap();
1501 let exif: crate::Exif = iter.into();
1502 drop(exif);
1503 let ptr_after_first = parser.state.cached_ptr_for_test();
1504
1505 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1506 let iter = parser.parse_exif(ms).unwrap();
1507 let _exif: crate::Exif = iter.into();
1508 let ptr_after_second = parser.state.cached_ptr_for_test();
1509
1510 assert!(
1511 ptr_after_first.is_some() && ptr_after_first == ptr_after_second,
1512 "expected recycled allocation, got {:?} -> {:?}",
1513 ptr_after_first,
1514 ptr_after_second
1515 );
1516 }
1517
1518 #[test]
1519 fn parser_new_does_no_upfront_allocation() {
1520 let parser = MediaParser::new();
1521 assert!(parser.state.cached_ptr_for_test().is_none());
1522 assert!(parser.state.buf_is_none_for_test());
1523 }
1524
1525 #[test]
1526 fn buffered_state_memory_mode_sets_and_reads() {
1527 let mut s = BufferedParserState::new();
1528 s.set_memory(bytes::Bytes::from_static(b"abcdefgh"));
1529 assert!(s.is_memory_mode());
1530 assert_eq!(s.buffer(), b"abcdefgh");
1531 s.set_position(3);
1532 assert_eq!(s.buffer(), b"defgh");
1533 }
1534
1535 #[test]
1536 fn buffered_state_share_buf_memory_mode_is_zero_copy() {
1537 let original = bytes::Bytes::from_static(b"the parser owns nothing here");
1538 let original_ptr = original.as_ptr();
1539 let mut s = BufferedParserState::new();
1540 s.set_memory(original);
1541 let (shared, position) = s.share_buf();
1542 assert_eq!(position, 0);
1543 assert_eq!(
1544 shared.as_ptr(),
1545 original_ptr,
1546 "memory share must be a Bytes::clone, not a Vec round-trip"
1547 );
1548 assert!(!s.is_memory_mode());
1551 }
1552
1553 #[test]
1554 fn buffered_state_reset_clears_memory() {
1555 let mut s = BufferedParserState::new();
1556 s.set_memory(bytes::Bytes::from_static(b"x"));
1557 s.reset();
1558 assert!(!s.is_memory_mode());
1559 assert_eq!(s.position, 0);
1560 }
1561
1562 #[test]
1563 fn buffered_state_acquire_buf_skips_in_memory_mode() {
1564 let mut s = BufferedParserState::new();
1565 s.set_memory(bytes::Bytes::from_static(b"data"));
1566 s.acquire_buf();
1567 assert!(s.buf.is_none());
1569 assert_eq!(s.buffer(), b"data");
1571 }
1572
1573 #[test]
1574 fn media_source_from_memory_image_jpg() {
1575 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1576 let ms = MediaSource::from_memory(raw).unwrap();
1577 assert_eq!(ms.kind(), MediaKind::Image);
1578 assert!(ms.memory.is_some());
1579 }
1580
1581 #[test]
1582 fn media_source_from_memory_track_mov() {
1583 let raw = std::fs::read("testdata/meta.mov").unwrap();
1584 let ms = MediaSource::from_memory(raw).unwrap();
1585 assert_eq!(ms.kind(), MediaKind::Track);
1586 }
1587
1588 #[test]
1589 fn media_source_from_memory_static_slice() {
1590 let raw: &'static [u8] = include_bytes!("../testdata/exif.jpg");
1591 let ms = MediaSource::from_memory(raw).unwrap();
1592 assert_eq!(ms.kind(), MediaKind::Image);
1593 }
1594
1595 #[test]
1596 fn media_source_from_memory_rejects_too_short() {
1597 let raw = vec![0u8; 4];
1598 let res = MediaSource::from_memory(raw);
1599 assert!(res.is_err());
1600 }
1601
1602 #[test]
1603 fn media_source_from_memory_rejects_unknown_mime() {
1604 let raw = vec![0xAAu8; 256];
1605 let res = MediaSource::from_memory(raw);
1606 assert!(res.is_err());
1607 }
1608
1609 #[test]
1610 fn parse_exif_unified_from_memory_jpg() {
1611 let mut parser = MediaParser::new();
1612 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1613 let ms = MediaSource::from_memory(raw).unwrap();
1614 let iter = parser.parse_exif(ms).unwrap();
1615 let exif: crate::Exif = iter.into();
1616 assert!(exif.get(crate::ExifTag::Make).is_some());
1617 }
1618
1619 #[test]
1620 fn parse_exif_unified_from_memory_heic() {
1621 let mut parser = MediaParser::new();
1622 let raw = std::fs::read("testdata/exif.heic").unwrap();
1623 let ms = MediaSource::from_memory(raw).unwrap();
1624 let iter = parser.parse_exif(ms).unwrap();
1625 let exif: crate::Exif = iter.into();
1626 assert_eq!(
1627 exif.get(crate::ExifTag::Make).and_then(|v| v.as_str()),
1628 Some("Apple")
1629 );
1630 }
1631
1632 #[test]
1633 fn parse_exif_unified_from_memory_zero_copy_preserved() {
1634 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1635 let bytes = bytes::Bytes::from(raw);
1636 let _original_ptr = bytes.as_ptr();
1637
1638 let mut parser = MediaParser::new();
1639 let ms = MediaSource::from_memory(bytes).unwrap();
1640 let iter = parser.parse_exif(ms).unwrap();
1641
1642 assert!(
1645 parser.state.cached_ptr_for_test().is_none(),
1646 "memory mode must not write to the streaming-buf recycle cache"
1647 );
1648 drop(iter);
1649 }
1650
1651 #[test]
1652 fn parse_exif_unified_on_track_returns_exif_not_found() {
1653 let mut parser = MediaParser::new();
1654 let raw = std::fs::read("testdata/meta.mov").unwrap();
1655 let ms = MediaSource::from_memory(raw).unwrap();
1656 let res = parser.parse_exif(ms);
1657 assert!(matches!(res, Err(crate::Error::ExifNotFound)));
1658 }
1659
1660 #[test]
1661 fn parse_exif_unified_on_truncated_returns_io_error() {
1662 let mut raw = std::fs::read("testdata/exif.jpg").unwrap();
1663 raw.truncate(200);
1664 let mut parser = MediaParser::new();
1665 let ms = MediaSource::from_memory(raw).unwrap();
1666 let res = parser.parse_exif(ms);
1667 assert!(
1668 res.is_err(),
1669 "expected error on truncated bytes, got {:?}",
1670 res
1671 );
1672 }
1673
1674 #[test]
1675 fn parse_exif_streaming_tiny_non_png_keeps_strict_eof() {
1676 let mut parser = MediaParser::new();
1682 let ms = MediaSource::open("testdata/compatible-brands.heic").unwrap();
1683 let res = parser.parse_exif(ms);
1684 assert!(res.is_err(), "expected Err for tiny HEIC, got {:?}", res);
1685 }
1686
1687 #[test]
1688 #[allow(deprecated)]
1689 fn media_source_from_bytes_image_jpg() {
1690 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1691 let ms = MediaSource::from_bytes(raw).unwrap();
1692 assert_eq!(ms.kind(), MediaKind::Image);
1693 assert!(ms.memory.is_some());
1694 }
1695
1696 #[test]
1697 #[allow(deprecated)]
1698 fn media_source_from_bytes_track_mov() {
1699 let raw = std::fs::read("testdata/meta.mov").unwrap();
1700 let ms = MediaSource::from_bytes(raw).unwrap();
1701 assert_eq!(ms.kind(), MediaKind::Track);
1702 }
1703
1704 #[test]
1705 #[allow(deprecated)]
1706 fn media_source_from_bytes_static_slice() {
1707 let raw: &'static [u8] = include_bytes!("../testdata/exif.jpg");
1710 let ms = MediaSource::from_bytes(raw).unwrap();
1711 assert_eq!(ms.kind(), MediaKind::Image);
1712 }
1713
1714 #[test]
1715 #[allow(deprecated)]
1716 fn media_source_from_bytes_rejects_too_short() {
1717 let raw = vec![0u8; 4];
1719 let res = MediaSource::from_bytes(raw);
1720 assert!(res.is_err(), "expected mime-detection error");
1721 }
1722
1723 #[test]
1724 #[allow(deprecated)]
1725 fn media_source_from_bytes_rejects_unknown_mime() {
1726 let raw = vec![0xAAu8; 256];
1729 let res = MediaSource::from_bytes(raw);
1730 assert!(
1731 res.is_err(),
1732 "expected mime-detection error for unknown bytes"
1733 );
1734 }
1735
1736 #[test]
1737 fn p4_5_baseline_exif_jpg_full_dump() {
1738 let mut parser = MediaParser::new();
1743 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1744 let iter: ExifIter = parser.parse_exif(ms).unwrap();
1745
1746 let mut entries: Vec<String> = iter
1747 .map(|e| {
1748 let tag_name = match e.tag() {
1749 crate::TagOrCode::Tag(t) => format!("{t}"),
1750 crate::TagOrCode::Unknown(c) => format!("0x{c:04x}"),
1751 };
1752 let value_str = e
1753 .value()
1754 .map(|v| format!("{v}"))
1755 .unwrap_or_else(|| "<err>".into());
1756 format!("{}.{}={:?}", e.ifd(), tag_name, value_str)
1757 })
1758 .collect();
1759 entries.sort();
1760 let snapshot = entries.join("\n");
1761
1762 assert!(
1766 entries.len() > 5,
1767 "expected >5 entries, got {}",
1768 entries.len()
1769 );
1770 assert!(snapshot.contains("Make"), "expected Make tag in snapshot");
1771 }
1772
1773 #[test]
1774 #[allow(deprecated)]
1775 fn parse_exif_from_bytes_jpg_basic() {
1776 let mut parser = MediaParser::new();
1777 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1778 let ms = MediaSource::from_bytes(raw).unwrap();
1779 let iter = parser.parse_exif_from_bytes(ms).unwrap();
1780 let exif: crate::Exif = iter.into();
1781 assert!(exif.get(crate::ExifTag::Make).is_some());
1782 }
1783
1784 #[test]
1785 #[allow(deprecated)]
1786 fn parse_exif_from_bytes_heic_basic() {
1787 let mut parser = MediaParser::new();
1788 let raw = std::fs::read("testdata/exif.heic").unwrap();
1789 let ms = MediaSource::from_bytes(raw).unwrap();
1790 let iter = parser.parse_exif_from_bytes(ms).unwrap();
1791 let exif: crate::Exif = iter.into();
1792 assert_eq!(
1793 exif.get(crate::ExifTag::Make).and_then(|v| v.as_str()),
1794 Some("Apple")
1795 );
1796 }
1797
1798 #[test]
1799 #[allow(deprecated)]
1800 fn parse_exif_from_bytes_zero_copy_shared_bytes() {
1801 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1804 let bytes = bytes::Bytes::from(raw);
1805 let original_ptr = bytes.as_ptr();
1806
1807 let mut parser = MediaParser::new();
1808 let ms = MediaSource::from_bytes(bytes).unwrap();
1809 let iter = parser.parse_exif_from_bytes(ms).unwrap();
1810
1811 assert!(
1814 parser.state.cached_ptr_for_test().is_none(),
1815 "memory mode must not poison the recycle cache"
1816 );
1817
1818 drop(iter);
1820
1821 let bytes2 = bytes::Bytes::from(std::fs::read("testdata/exif.jpg").unwrap());
1824 let ms2 = MediaSource::from_bytes(bytes2.clone()).unwrap();
1825 let _iter2 = parser.parse_exif_from_bytes(ms2).unwrap();
1826 let _ = original_ptr; }
1830
1831 #[test]
1832 #[allow(deprecated)]
1833 fn parse_exif_from_bytes_on_track_returns_exif_not_found() {
1834 let mut parser = MediaParser::new();
1835 let raw = std::fs::read("testdata/meta.mov").unwrap();
1836 let ms = MediaSource::from_bytes(raw).unwrap();
1837 let res = parser.parse_exif_from_bytes(ms);
1838 assert!(matches!(res, Err(crate::Error::ExifNotFound)));
1839 }
1840
1841 #[test]
1842 #[allow(deprecated)]
1843 fn parse_exif_from_bytes_on_truncated_returns_io_error() {
1844 let mut raw = std::fs::read("testdata/exif.jpg").unwrap();
1848 raw.truncate(200);
1849 let mut parser = MediaParser::new();
1850 let ms = MediaSource::from_bytes(raw).unwrap();
1851 let res = parser.parse_exif_from_bytes(ms);
1852 assert!(
1853 res.is_err(),
1854 "expected error on truncated bytes, got {:?}",
1855 res
1856 );
1857 }
1858
1859 #[test]
1860 #[allow(deprecated)]
1861 fn parse_track_from_bytes_mov_basic() {
1862 let mut parser = MediaParser::new();
1863 let raw = std::fs::read("testdata/meta.mov").unwrap();
1864 let ms = MediaSource::from_bytes(raw).unwrap();
1865 let info = parser.parse_track_from_bytes(ms).unwrap();
1866 assert_eq!(info.get(crate::TrackInfoTag::Make), Some(&"Apple".into()));
1867 assert_eq!(
1868 info.get(crate::TrackInfoTag::Model),
1869 Some(&"iPhone X".into())
1870 );
1871 }
1872
1873 #[test]
1874 #[allow(deprecated)]
1875 fn parse_track_from_bytes_mp4_basic() {
1876 let mut parser = MediaParser::new();
1877 let raw = std::fs::read("testdata/meta.mp4").unwrap();
1878 let ms = MediaSource::from_bytes(raw).unwrap();
1879 let info = parser.parse_track_from_bytes(ms).unwrap();
1880 assert!(info.get(crate::TrackInfoTag::CreateDate).is_some());
1881 }
1882
1883 #[test]
1884 #[allow(deprecated)]
1885 fn parse_track_from_bytes_mkv_basic() {
1886 let mut parser = MediaParser::new();
1887 let raw = std::fs::read("testdata/mkv_640x360.mkv").unwrap();
1888 let ms = MediaSource::from_bytes(raw).unwrap();
1889 let info = parser.parse_track_from_bytes(ms).unwrap();
1890 assert_eq!(
1891 info.get(crate::TrackInfoTag::Width),
1892 Some(&(640_u32.into()))
1893 );
1894 }
1895
1896 #[test]
1897 #[allow(deprecated)]
1898 fn parse_track_from_bytes_on_image_returns_track_not_found() {
1899 let mut parser = MediaParser::new();
1900 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1901 let ms = MediaSource::from_bytes(raw).unwrap();
1902 let res = parser.parse_track_from_bytes(ms);
1903 assert!(matches!(res, Err(crate::Error::TrackNotFound)));
1904 }
1905
1906 #[test]
1907 fn parse_track_unified_from_memory_mov() {
1908 let mut parser = MediaParser::new();
1909 let raw = std::fs::read("testdata/meta.mov").unwrap();
1910 let ms = MediaSource::from_memory(raw).unwrap();
1911 let info = parser.parse_track(ms).unwrap();
1912 assert_eq!(info.get(crate::TrackInfoTag::Make), Some(&"Apple".into()));
1913 }
1914
1915 #[test]
1916 fn parse_track_unified_from_memory_mp4() {
1917 let mut parser = MediaParser::new();
1918 let raw = std::fs::read("testdata/meta.mp4").unwrap();
1919 let ms = MediaSource::from_memory(raw).unwrap();
1920 let info = parser.parse_track(ms).unwrap();
1921 assert!(info.get(crate::TrackInfoTag::CreateDate).is_some());
1922 }
1923
1924 #[test]
1925 fn parse_track_unified_on_image_returns_track_not_found() {
1926 let mut parser = MediaParser::new();
1927 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1928 let ms = MediaSource::from_memory(raw).unwrap();
1929 let res = parser.parse_track(ms);
1930 assert!(matches!(res, Err(crate::Error::TrackNotFound)));
1931 }
1932
1933 #[test]
1934 fn parse_image_metadata_jpeg_returns_exif_only() {
1935 let mut parser = MediaParser::new();
1936 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1937 let img = parser.parse_image_metadata(ms).unwrap();
1938 assert!(img.exif.is_some());
1939 assert!(img.format.is_none());
1940 }
1941
1942 #[test]
1943 fn parse_image_metadata_jpeg_from_memory() {
1944 let mut parser = MediaParser::new();
1945 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1946 let ms = MediaSource::from_memory(raw).unwrap();
1947 let img = parser.parse_image_metadata(ms).unwrap();
1948 assert!(img.exif.is_some());
1949 assert!(img.format.is_none());
1950 }
1951
1952 #[test]
1953 fn parse_image_metadata_on_track_returns_exif_not_found() {
1954 let mut parser = MediaParser::new();
1955 let ms = MediaSource::open("testdata/meta.mov").unwrap();
1956 let res = parser.parse_image_metadata(ms);
1957 assert!(matches!(res, Err(crate::Error::ExifNotFound)));
1958 }
1959
1960 #[test]
1965 fn parse_image_metadata_png_large_idat_streaming() {
1966 use std::io::Cursor;
1967 let png = build_png_with_large_idat(INIT_BUF_SIZE + 1024);
1968 let mut parser = MediaParser::new();
1969 let ms = MediaSource::seekable(Cursor::new(png)).unwrap();
1970 assert_eq!(ms.kind(), crate::MediaKind::Image);
1971 let res = parser.parse_image_metadata(ms);
1972 assert!(
1973 matches!(res, Err(crate::Error::ExifNotFound)),
1974 "expected ExifNotFound on PNG with no EXIF / tEXt; got {res:?}"
1975 );
1976 }
1977
1978 #[test]
1981 fn parse_exif_png_large_idat_streaming() {
1982 use std::io::Cursor;
1983 let png = build_png_with_large_idat(INIT_BUF_SIZE + 1024);
1984 let mut parser = MediaParser::new();
1985 let ms = MediaSource::seekable(Cursor::new(png)).unwrap();
1986 let res = parser.parse_exif(ms);
1987 assert!(
1988 matches!(res, Err(crate::Error::ExifNotFound)),
1989 "expected ExifNotFound; got {res:?}"
1990 );
1991 }
1992
1993 #[test]
1997 fn parse_image_metadata_png_large_idat_unseekable() {
1998 let png = build_png_with_large_idat(INIT_BUF_SIZE + 1024);
1999 let mut parser = MediaParser::new();
2000 let ms = MediaSource::unseekable(NoSeek(std::io::Cursor::new(png))).unwrap();
2001 let res = parser.parse_image_metadata(ms);
2002 assert!(
2003 matches!(res, Err(crate::Error::ExifNotFound)),
2004 "expected ExifNotFound; got {res:?}"
2005 );
2006 }
2007
2008 struct NoSeek<R>(R);
2011 impl<R: io::Read> io::Read for NoSeek<R> {
2012 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
2013 self.0.read(buf)
2014 }
2015 }
2016
2017 fn build_png_with_large_idat(idat_body: usize) -> Vec<u8> {
2018 let mut out = Vec::new();
2019 out.extend_from_slice(b"\x89PNG\r\n\x1a\n");
2020 out.extend_from_slice(&13u32.to_be_bytes());
2022 out.extend_from_slice(b"IHDR");
2023 out.extend_from_slice(&[0, 0, 0, 1, 0, 0, 0, 1, 8, 0, 0, 0, 0]);
2024 out.extend_from_slice(&[0, 0, 0, 0]); out.extend_from_slice(&(idat_body as u32).to_be_bytes());
2027 out.extend_from_slice(b"IDAT");
2028 out.resize(out.len() + idat_body, 0);
2029 out.extend_from_slice(&[0, 0, 0, 0]); out.extend_from_slice(&0u32.to_be_bytes());
2032 out.extend_from_slice(b"IEND");
2033 out.extend_from_slice(&[0, 0, 0, 0]); out
2035 }
2036
2037 #[cfg(feature = "tokio-fs")]
2038 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2039 async fn parse_image_metadata_async_jpeg() {
2040 use crate::parser_async::AsyncMediaSource;
2041 let mut parser = MediaParser::new();
2042 let ms = AsyncMediaSource::open("testdata/exif.jpg").await.unwrap();
2043 let img = parser.parse_image_metadata_async(ms).await.unwrap();
2044 assert!(img.exif.is_some());
2045 assert!(img.format.is_none());
2046 }
2047
2048 #[cfg(feature = "tokio-fs")]
2049 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2050 async fn async_media_source_from_memory_image_jpg() {
2051 use crate::parser_async::AsyncMediaSource;
2052 let raw = std::fs::read("testdata/exif.jpg").unwrap();
2053 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2054 assert_eq!(ms.kind(), MediaKind::Image);
2055 assert!(ms.memory.is_some());
2056 }
2057
2058 #[cfg(feature = "tokio-fs")]
2059 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2060 async fn async_media_source_from_memory_track_mov() {
2061 use crate::parser_async::AsyncMediaSource;
2062 let raw = std::fs::read("testdata/meta.mov").unwrap();
2063 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2064 assert_eq!(ms.kind(), MediaKind::Track);
2065 }
2066
2067 #[cfg(feature = "tokio-fs")]
2068 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2069 async fn async_media_source_from_memory_rejects_unknown_mime() {
2070 use crate::parser_async::AsyncMediaSource;
2071 let raw = vec![0xAAu8; 256];
2072 let res = AsyncMediaSource::from_memory(raw);
2073 assert!(res.is_err());
2074 }
2075
2076 #[cfg(feature = "tokio-fs")]
2077 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2078 async fn parse_exif_async_from_memory_jpg() {
2079 use crate::parser_async::AsyncMediaSource;
2080 let raw = std::fs::read("testdata/exif.jpg").unwrap();
2081 let mut parser = MediaParser::new();
2082 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2083 let iter = parser.parse_exif_async(ms).await.unwrap();
2084 let exif: crate::Exif = iter.into();
2085 assert!(exif.get(crate::ExifTag::Make).is_some());
2086 }
2087
2088 #[cfg(feature = "tokio-fs")]
2089 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2090 async fn parse_exif_async_from_memory_zero_copy_preserved() {
2091 use crate::parser_async::AsyncMediaSource;
2092 let raw = std::fs::read("testdata/exif.jpg").unwrap();
2093 let bytes = bytes::Bytes::from(raw);
2094 let mut parser = MediaParser::new();
2095 let ms = AsyncMediaSource::from_memory(bytes).unwrap();
2096 let iter = parser.parse_exif_async(ms).await.unwrap();
2097 assert!(
2100 parser.state.cached_ptr_for_test().is_none(),
2101 "async memory mode must not write to the streaming-buf recycle cache"
2102 );
2103 drop(iter);
2104 }
2105
2106 #[cfg(feature = "tokio-fs")]
2107 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2108 async fn parse_track_async_from_memory_mov() {
2109 use crate::parser_async::AsyncMediaSource;
2110 let raw = std::fs::read("testdata/meta.mov").unwrap();
2111 let mut parser = MediaParser::new();
2112 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2113 let info = parser.parse_track_async(ms).await.unwrap();
2114 assert_eq!(info.get(crate::TrackInfoTag::Make), Some(&"Apple".into()));
2115 }
2116
2117 #[cfg(feature = "tokio-fs")]
2118 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2119 async fn parse_image_metadata_async_from_memory_png() {
2120 use crate::parser_async::AsyncMediaSource;
2121 let raw = std::fs::read("testdata/exif.png").unwrap();
2122 let mut parser = MediaParser::new();
2123 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2124 let img = parser.parse_image_metadata_async(ms).await.unwrap();
2125 assert!(img.exif.is_some());
2126 assert!(img.format.is_some());
2127 }
2128
2129 #[cfg(feature = "tokio-fs")]
2134 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2135 async fn parse_image_metadata_async_png_large_idat_streaming() {
2136 use crate::parser_async::AsyncMediaSource;
2137 let png = build_png_with_large_idat(INIT_BUF_SIZE + 1024);
2138 let path =
2142 std::env::temp_dir().join(format!("nom-exif-issue55-{}.png", std::process::id()));
2143 tokio::fs::write(&path, &png).await.unwrap();
2144 let mut parser = MediaParser::new();
2145 let ms = AsyncMediaSource::open(&path).await.unwrap();
2146 let res = parser.parse_image_metadata_async(ms).await;
2147 let _ = tokio::fs::remove_file(&path).await;
2148 assert!(
2149 matches!(res, Err(crate::Error::ExifNotFound)),
2150 "expected ExifNotFound; got {res:?}"
2151 );
2152 }
2153
2154 #[cfg(feature = "tokio")]
2155 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2156 async fn parse_image_metadata_async_from_memory_text_only_png() {
2157 use crate::parser_async::AsyncMediaSource;
2161 let raw = std::fs::read("testdata/text-only.png").unwrap();
2162 let mut parser = MediaParser::new();
2163 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2164 let img = parser.parse_image_metadata_async(ms).await.unwrap();
2165 assert!(img.exif.is_none());
2166 assert!(img.format.is_some());
2167 }
2168
2169 #[test]
2178 fn parse_exif_streaming_text_only_png_returns_exif_not_found() {
2179 let mut parser = MediaParser::new();
2183 let ms = MediaSource::open("testdata/text-only.png").unwrap();
2184 let res = parser.parse_exif(ms);
2185 assert!(
2186 matches!(res, Err(crate::Error::ExifNotFound)),
2187 "expected ExifNotFound for tEXt-only PNG, got {:?}",
2188 res
2189 );
2190 }
2191
2192 #[test]
2193 fn parse_image_metadata_streaming_text_only_png() {
2194 let mut parser = MediaParser::new();
2195 let ms = MediaSource::open("testdata/text-only.png").unwrap();
2196 let img = parser.parse_image_metadata(ms).unwrap();
2197 assert!(img.exif.is_none());
2198 assert!(img.format.is_some());
2199 }
2200
2201 #[cfg(feature = "tokio")]
2202 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2203 async fn parse_exif_async_streaming_text_only_png_returns_exif_not_found() {
2204 use crate::parser_async::AsyncMediaSource;
2205 let mut parser = MediaParser::new();
2206 let f = tokio::fs::File::open("testdata/text-only.png")
2207 .await
2208 .unwrap();
2209 let ms = AsyncMediaSource::seekable(f).await.unwrap();
2210 let res = parser.parse_exif_async(ms).await;
2211 assert!(
2212 matches!(res, Err(crate::Error::ExifNotFound)),
2213 "expected ExifNotFound for tEXt-only PNG via async streaming, got {:?}",
2214 res
2215 );
2216 }
2217
2218 #[cfg(feature = "tokio")]
2219 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2220 async fn parse_image_metadata_async_streaming_text_only_png() {
2221 use crate::parser_async::AsyncMediaSource;
2222 let mut parser = MediaParser::new();
2223 let f = tokio::fs::File::open("testdata/text-only.png")
2224 .await
2225 .unwrap();
2226 let ms = AsyncMediaSource::seekable(f).await.unwrap();
2227 let img = parser.parse_image_metadata_async(ms).await.unwrap();
2228 assert!(img.exif.is_none());
2229 assert!(img.format.is_some());
2230 }
2231
2232 fn webp_minimal_tiff_le() -> Vec<u8> {
2234 let mut t = Vec::new();
2236 t.extend_from_slice(b"II");
2237 t.extend_from_slice(&[0x2a, 0x00]);
2238 t.extend_from_slice(&[0x08, 0, 0, 0]); t.extend_from_slice(&[0x01, 0x00]); t.extend_from_slice(&[0x0f, 0x01]); t.extend_from_slice(&[0x02, 0x00]); t.extend_from_slice(&[0x02, 0x00, 0x00, 0x00]); t.extend_from_slice(b"A\0\0\0"); t.extend_from_slice(&[0, 0, 0, 0]); t
2247 }
2248
2249 fn webp_with_exif(tiff: &[u8]) -> Vec<u8> {
2250 let mut exif_chunk = Vec::new();
2251 exif_chunk.extend_from_slice(b"EXIF");
2252 exif_chunk.extend_from_slice(&(tiff.len() as u32).to_le_bytes());
2253 exif_chunk.extend_from_slice(tiff);
2254 if tiff.len() % 2 == 1 {
2255 exif_chunk.push(0);
2256 }
2257 let mut vp8x = Vec::new();
2258 vp8x.extend_from_slice(b"VP8X");
2259 vp8x.extend_from_slice(&10u32.to_le_bytes());
2260 vp8x.extend_from_slice(&[0u8; 10]);
2261
2262 let mut body = Vec::new();
2263 body.extend_from_slice(b"WEBP");
2264 body.extend_from_slice(&vp8x);
2265 body.extend_from_slice(&exif_chunk);
2266
2267 let mut out = Vec::new();
2268 out.extend_from_slice(b"RIFF");
2269 out.extend_from_slice(&(body.len() as u32).to_le_bytes());
2270 out.extend_from_slice(&body);
2271 out
2272 }
2273
2274 #[test]
2275 fn parse_exif_webp_from_memory() {
2276 let tiff = webp_minimal_tiff_le();
2277 let buf = webp_with_exif(&tiff);
2278 let mut parser = MediaParser::new();
2279 let ms = MediaSource::from_memory(buf).unwrap();
2280 let iter = parser.parse_exif(ms).unwrap();
2281 let exif: crate::Exif = iter.into();
2282 assert_eq!(
2283 exif.get(crate::ExifTag::Make).and_then(|v| v.as_str()),
2284 Some("A")
2285 );
2286 }
2287
2288 #[test]
2289 fn parse_exif_webp_no_exif_returns_not_found() {
2290 let mut vp8x = Vec::new();
2292 vp8x.extend_from_slice(b"VP8X");
2293 vp8x.extend_from_slice(&10u32.to_le_bytes());
2294 vp8x.extend_from_slice(&[0u8; 10]);
2295 let mut body = Vec::new();
2296 body.extend_from_slice(b"WEBP");
2297 body.extend_from_slice(&vp8x);
2298 let mut buf = Vec::new();
2299 buf.extend_from_slice(b"RIFF");
2300 buf.extend_from_slice(&(body.len() as u32).to_le_bytes());
2301 buf.extend_from_slice(&body);
2302
2303 let mut parser = MediaParser::new();
2304 let ms = MediaSource::from_memory(buf).unwrap();
2305 let res = parser.parse_exif(ms);
2306 assert!(matches!(res, Err(crate::Error::ExifNotFound)));
2307 }
2308
2309 #[test]
2310 fn parse_exif_webp_streaming_small_file() {
2311 use std::io::Cursor;
2314 let tiff = webp_minimal_tiff_le();
2315 let buf = webp_with_exif(&tiff);
2316 let mut parser = MediaParser::new();
2317 let ms = MediaSource::seekable(Cursor::new(buf)).unwrap();
2318 let iter = parser.parse_exif(ms).unwrap();
2319 let exif: crate::Exif = iter.into();
2320 assert_eq!(
2321 exif.get(crate::ExifTag::Make).and_then(|v| v.as_str()),
2322 Some("A")
2323 );
2324 }
2325
2326 #[cfg(feature = "tokio")]
2327 #[tokio::test]
2328 async fn parse_exif_webp_async_from_memory() {
2329 use crate::AsyncMediaSource;
2330 let tiff = webp_minimal_tiff_le();
2331 let buf = webp_with_exif(&tiff);
2332 let mut parser = MediaParser::new();
2333 let ms = AsyncMediaSource::from_memory(buf).unwrap();
2334 let iter = parser.parse_exif_async(ms).await.unwrap();
2335 let exif: crate::Exif = iter.into();
2336 assert_eq!(
2337 exif.get(crate::ExifTag::Make).and_then(|v| v.as_str()),
2338 Some("A")
2339 );
2340 }
2341}