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 Png(crate::png::PngState),
508 WebpPastHeader(usize),
513}
514
515impl Display for ParsingState {
516 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
517 match self {
518 ParsingState::TiffHeader(h) => Display::fmt(&format!("ParsingState: {h:?})"), f),
519 ParsingState::HeifExifSize(n) => Display::fmt(&format!("ParsingState: {n}"), f),
520 ParsingState::Cr3ExifSize(n) => Display::fmt(&format!("ParsingState: {n}"), f),
521 ParsingState::Png(_) => f.write_str("ParsingState: Png"),
522 ParsingState::WebpPastHeader(n) => {
523 Display::fmt(&format!("ParsingState: WebpPastHeader({n})"), f)
524 }
525 }
526 }
527}
528
529pub(crate) trait BufParser: Buf + Debug {
532 fn fill_buf<R: Read>(&mut self, reader: &mut R, size: usize) -> io::Result<usize>;
533
534 fn load_and_parse<R: Read, P, O>(
535 &mut self,
536 reader: &mut R,
537 skip_by_seek: SkipBySeekFn<R>,
538 mut parse: P,
539 ) -> Result<O, ParsedError>
540 where
541 P: FnMut(&[u8], Option<ParsingState>) -> Result<O, ParsingErrorState>,
542 {
543 self.load_and_parse_with_offset(
544 reader,
545 skip_by_seek,
546 |data, _, state| parse(data, state),
547 0,
548 )
549 }
550
551 #[tracing::instrument(skip_all)]
552 fn load_and_parse_with_offset<R: Read, P, O>(
553 &mut self,
554 reader: &mut R,
555 skip_by_seek: SkipBySeekFn<R>,
556 mut parse: P,
557 offset: usize,
558 ) -> Result<O, ParsedError>
559 where
560 P: FnMut(&[u8], usize, Option<ParsingState>) -> Result<O, ParsingErrorState>,
561 {
562 if offset >= self.buffer().len() {
563 self.fill_buf(reader, MIN_GROW_SIZE)?;
564 }
565 let mut parsing_state: Option<ParsingState> = None;
566 loop {
567 match parse_loop_step(self.buffer(), offset, &mut parsing_state, &mut parse) {
568 LoopAction::Done(o) => return Ok(o),
569 LoopAction::NeedFill(needed) => {
570 let to_read = max(needed, MIN_GROW_SIZE);
571 let n = self.fill_buf(reader, to_read)?;
572 if n == 0 {
573 return Err(ParsedError::NoEnoughBytes);
574 }
575 }
576 LoopAction::Skip(n) => {
577 self.clear_and_skip(reader, skip_by_seek, n)?;
578 }
579 LoopAction::Failed { kind, message } => {
580 return Err(ParsedError::Failed { kind, message })
581 }
582 }
583 }
584 }
585
586 #[tracing::instrument(skip(reader, skip_by_seek))]
587 fn clear_and_skip<R: Read>(
588 &mut self,
589 reader: &mut R,
590 skip_by_seek: SkipBySeekFn<R>,
591 n: usize,
592 ) -> Result<(), ParsedError> {
593 match clear_and_skip_decide(self.buffer().len(), n) {
594 SkipPlan::AdvanceOnly => {
595 self.set_position(self.position() + n);
596 Ok(())
597 }
598 SkipPlan::ClearAndSkip { extra: skip_n } => {
599 self.clear();
600 let done = (skip_by_seek)(
601 reader,
602 skip_n.try_into().map_err(|_| ParsedError::Failed {
603 kind: crate::error::MalformedKind::IsoBmffBox,
607 message: "skip too many bytes".into(),
608 })?,
609 )?;
610 if !done {
611 let mut skipped = 0;
612 while skipped < skip_n {
613 let mut to_skip = skip_n - skipped;
614 to_skip = min(to_skip, MAX_PARSE_BUF_SIZE);
615 let n = self.fill_buf(reader, to_skip)?;
616 skipped += n;
617 if skipped <= skip_n {
618 self.clear();
619 } else {
620 let remain = skipped - skip_n;
621 self.set_position(self.buffer().len() - remain);
622 break;
623 }
624 }
625 }
626
627 if self.buffer().is_empty() {
628 self.fill_buf(reader, MIN_GROW_SIZE)?;
629 }
630 Ok(())
631 }
632 }
633 }
634}
635
636impl BufParser for MediaParser {
637 #[tracing::instrument(skip(self, reader), fields(buf_len=self.state.buffer().len()))]
638 fn fill_buf<R: Read>(&mut self, reader: &mut R, size: usize) -> io::Result<usize> {
639 if self.state.is_memory_mode() {
640 return Err(std::io::ErrorKind::UnexpectedEof.into());
644 }
645 check_fill_size(self.state.buf().len(), size)?;
646
647 let n = reader.take(size as u64).read_to_end(self.state.buf_mut())?;
653 if n == 0 {
654 tracing::error!(buf_len = self.state.buf().len(), "fill_buf: EOF");
655 return Err(std::io::ErrorKind::UnexpectedEof.into());
656 }
657
658 tracing::debug!(
659 ?size,
660 ?n,
661 buf_len = self.state.buf().len(),
662 "fill_buf: read bytes"
663 );
664
665 Ok(n)
666 }
667}
668
669impl Buf for MediaParser {
670 fn buffer(&self) -> &[u8] {
671 self.state.buffer()
672 }
673
674 fn clear(&mut self) {
675 self.state.clear();
676 }
677
678 fn set_position(&mut self, pos: usize) {
679 self.state.set_position(pos);
680 }
681
682 fn position(&self) -> usize {
683 self.state.position()
684 }
685}
686
687pub struct MediaParser {
736 state: BufferedParserState,
737}
738
739impl Debug for MediaParser {
740 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
741 f.debug_struct("MediaParser")
742 .field("state", &self.state)
743 .finish_non_exhaustive()
744 }
745}
746
747impl Default for MediaParser {
748 fn default() -> Self {
749 Self {
750 state: BufferedParserState::new(),
751 }
752 }
753}
754
755pub(crate) trait ShareBuf {
756 fn share_buf(&mut self) -> (bytes::Bytes, usize);
761}
762
763impl ShareBuf for MediaParser {
764 fn share_buf(&mut self) -> (bytes::Bytes, usize) {
765 self.state.share_buf()
766 }
767}
768
769impl MediaParser {
770 pub fn new() -> Self {
771 Self::default()
772 }
773
774 pub fn parse_exif<R: Read>(&mut self, mut ms: MediaSource<R>) -> crate::Result<ExifIter> {
786 self.reset();
787 let res: crate::Result<ExifIter> = (|| {
788 if let Some(memory) = ms.memory.take() {
789 self.state.set_memory(memory);
791 if !matches!(ms.mime, crate::file::MediaMime::Image(_)) {
792 return Err(crate::Error::ExifNotFound);
793 }
794 crate::exif::parse_exif_iter(
795 self,
796 ms.mime.unwrap_image(),
797 &mut ms.reader,
798 ms.skip_by_seek,
799 )
800 } else {
801 self.acquire_buf();
803 self.buf_mut().append(&mut ms.buf);
804 let is_png = matches!(
811 ms.mime,
812 crate::file::MediaMime::Image(
813 crate::file::MediaMimeImage::Png | crate::file::MediaMimeImage::Webp
814 )
815 );
816 match self.fill_buf(&mut ms.reader, INIT_BUF_SIZE) {
817 Ok(_) => {}
818 Err(e)
819 if is_png
820 && !self.buffer().is_empty()
821 && e.kind() == io::ErrorKind::UnexpectedEof => {}
822 Err(e) => return Err(e.into()),
823 }
824 if !matches!(ms.mime, crate::file::MediaMime::Image(_)) {
825 return Err(crate::Error::ExifNotFound);
826 }
827 crate::exif::parse_exif_iter(
828 self,
829 ms.mime.unwrap_image(),
830 &mut ms.reader,
831 ms.skip_by_seek,
832 )
833 }
834 })();
835 self.reset();
836 res
837 }
838
839 pub fn parse_track<R: Read>(&mut self, mut ms: MediaSource<R>) -> crate::Result<TrackInfo> {
849 self.reset();
850 let res: crate::Result<TrackInfo> = (|| {
851 if let Some(memory) = ms.memory.take() {
852 self.state.set_memory(memory);
854 let mime_track = match ms.mime {
855 crate::file::MediaMime::Image(_) => return Err(crate::Error::TrackNotFound),
856 crate::file::MediaMime::Track(t) => t,
857 };
858 let out = self.load_and_parse(&mut ms.reader, ms.skip_by_seek, |data, _| {
859 crate::video::parse_track_info(data, mime_track)
860 .map_err(|e| ParsingErrorState::new(e, None))
861 })?;
862 Ok(out)
863 } else {
864 self.acquire_buf();
866 self.buf_mut().append(&mut ms.buf);
867 self.fill_buf(&mut ms.reader, INIT_BUF_SIZE)?;
868 match ms.mime {
869 crate::file::MediaMime::Image(crate::file::MediaMimeImage::Jpeg) => {
870 self.parse_jpeg_motion_photo(&mut ms.reader)
871 }
872 crate::file::MediaMime::Image(_) => Err(crate::Error::TrackNotFound),
873 crate::file::MediaMime::Track(mime_track) => {
874 let skip = ms.skip_by_seek;
875 Ok(self.load_and_parse(ms.reader.by_ref(), skip, |data, _| {
876 crate::video::parse_track_info(data, mime_track)
877 .map_err(|e| ParsingErrorState::new(e, None))
878 })?)
879 }
880 }
881 }
882 })();
883 self.reset();
884 res
885 }
886
887 fn parse_jpeg_motion_photo<R: Read>(&mut self, reader: &mut R) -> crate::Result<TrackInfo> {
892 reader.read_to_end(self.buf_mut())?;
895 let buf = self.buf_mut();
896 let Some(offset) = crate::jpeg::find_motion_photo_offset(buf) else {
897 return Err(crate::Error::TrackNotFound);
898 };
899 let trailer_start = (buf.len() as u64)
900 .checked_sub(offset)
901 .ok_or(crate::Error::TrackNotFound)? as usize;
902 let trailer = &buf[trailer_start..];
903
904 let trailer_mime =
907 crate::file::MediaMime::try_from(trailer).map_err(|_| crate::Error::TrackNotFound)?;
908 let mime_track = match trailer_mime {
909 crate::file::MediaMime::Track(t) => t,
910 crate::file::MediaMime::Image(_) => return Err(crate::Error::TrackNotFound),
911 };
912 crate::video::parse_track_info(trailer, mime_track).map_err(|e| match e {
913 crate::error::ParsingError::Need(_) | crate::error::ParsingError::ClearAndSkip(_) => {
914 crate::Error::UnexpectedEof {
915 context: "motion-photo trailer",
916 }
917 }
918 crate::error::ParsingError::Failed { kind, message } => {
919 crate::Error::Malformed { kind, message }
920 }
921 })
922 }
923
924 #[deprecated(
930 since = "3.3.0",
931 note = "Use `parse_exif` directly — it now accepts memory-mode \
932 sources built via `MediaSource::from_memory`."
933 )]
934 pub fn parse_exif_from_bytes(&mut self, ms: MediaSource<()>) -> crate::Result<ExifIter> {
935 self.parse_exif(ms.into_empty())
936 }
937
938 #[deprecated(
941 since = "3.3.0",
942 note = "Use `parse_track` with `MediaSource::from_memory`."
943 )]
944 pub fn parse_track_from_bytes(&mut self, ms: MediaSource<()>) -> crate::Result<TrackInfo> {
945 self.parse_track(ms.into_empty())
946 }
947
948 pub fn parse_image_metadata<R: Read>(
960 &mut self,
961 mut ms: MediaSource<R>,
962 ) -> crate::Result<crate::image_metadata::ImageMetadata<crate::ExifIter>> {
963 self.reset();
964 let res: crate::Result<crate::image_metadata::ImageMetadata<crate::ExifIter>> = (|| {
965 let mime_img = match ms.mime {
967 crate::file::MediaMime::Image(img) => img,
968 crate::file::MediaMime::Track(_) => return Err(crate::Error::ExifNotFound),
969 };
970
971 if let Some(memory) = ms.memory.take() {
973 self.state.set_memory(memory);
974 } else {
975 self.acquire_buf();
976 self.buf_mut().append(&mut ms.buf);
977 let is_png = matches!(
984 mime_img,
985 crate::file::MediaMimeImage::Png | crate::file::MediaMimeImage::Webp
986 );
987 match self.fill_buf(&mut ms.reader, INIT_BUF_SIZE) {
988 Ok(_) => {}
989 Err(e)
990 if is_png
991 && !self.buffer().is_empty()
992 && e.kind() == io::ErrorKind::UnexpectedEof => {}
993 Err(e) => return Err(e.into()),
994 }
995 }
996
997 if mime_img == crate::file::MediaMimeImage::Png {
998 let (exif, text_chunks) =
999 crate::exif::parse_png_full(self, &mut ms.reader, ms.skip_by_seek)?;
1000 let format = if text_chunks.is_empty() {
1001 None
1002 } else {
1003 Some(crate::ImageFormatMetadata::Png(crate::PngTextChunks {
1004 entries: text_chunks,
1005 }))
1006 };
1007 if exif.is_none() && format.is_none() {
1008 return Err(crate::Error::ExifNotFound);
1009 }
1010 Ok(crate::ImageMetadata { exif, format })
1011 } else {
1012 let iter =
1014 crate::exif::parse_exif_iter(self, mime_img, &mut ms.reader, ms.skip_by_seek)?;
1015 Ok(crate::ImageMetadata {
1016 exif: Some(iter),
1017 format: None,
1018 })
1019 }
1020 })(
1021 );
1022 self.reset();
1023 res
1024 }
1025
1026 fn reset(&mut self) {
1027 self.state.reset();
1028 }
1029
1030 fn buf_mut(&mut self) -> &mut Vec<u8> {
1031 self.state.buf_mut()
1032 }
1033
1034 fn acquire_buf(&mut self) {
1035 self.state.acquire_buf();
1036 }
1037}
1038
1039#[cfg(feature = "tokio")]
1040mod tokio_impl {
1041 use super::*;
1042 use crate::error::ParsingErrorState;
1043 use crate::parser_async::{AsyncBufParser, AsyncMediaSource};
1044 use tokio::io::{AsyncRead, AsyncReadExt};
1045
1046 impl AsyncBufParser for MediaParser {
1047 async fn fill_buf<R: AsyncRead + Unpin>(
1048 &mut self,
1049 reader: &mut R,
1050 size: usize,
1051 ) -> std::io::Result<usize> {
1052 if self.state.is_memory_mode() {
1053 return Err(std::io::ErrorKind::UnexpectedEof.into());
1057 }
1058 check_fill_size(self.state.buf().len(), size)?;
1059 let n = reader
1061 .take(size as u64)
1062 .read_to_end(self.state.buf_mut())
1063 .await?;
1064 if n == 0 {
1065 return Err(std::io::ErrorKind::UnexpectedEof.into());
1066 }
1067 Ok(n)
1068 }
1069 }
1070
1071 impl MediaParser {
1072 pub async fn parse_exif_async<R: AsyncRead + Unpin + Send>(
1079 &mut self,
1080 mut ms: AsyncMediaSource<R>,
1081 ) -> crate::Result<ExifIter> {
1082 self.reset();
1083 let res: crate::Result<ExifIter> = async {
1084 if let Some(memory) = ms.memory.take() {
1085 self.state.set_memory(memory);
1087 if !matches!(ms.mime, crate::file::MediaMime::Image(_)) {
1088 return Err(crate::Error::ExifNotFound);
1089 }
1090 crate::exif::parse_exif_iter_async(
1091 self,
1092 ms.mime.unwrap_image(),
1093 &mut ms.reader,
1094 ms.skip_by_seek,
1095 )
1096 .await
1097 } else {
1098 self.acquire_buf();
1099 self.buf_mut().append(&mut ms.buf);
1100 let is_png = matches!(
1106 ms.mime,
1107 crate::file::MediaMime::Image(
1108 crate::file::MediaMimeImage::Png | crate::file::MediaMimeImage::Webp
1109 )
1110 );
1111 match <Self as AsyncBufParser>::fill_buf(self, &mut ms.reader, INIT_BUF_SIZE)
1112 .await
1113 {
1114 Ok(_) => {}
1115 Err(e)
1116 if is_png
1117 && !self.buffer().is_empty()
1118 && e.kind() == io::ErrorKind::UnexpectedEof => {}
1119 Err(e) => return Err(e.into()),
1120 }
1121 if !matches!(ms.mime, crate::file::MediaMime::Image(_)) {
1122 return Err(crate::Error::ExifNotFound);
1123 }
1124 crate::exif::parse_exif_iter_async(
1125 self,
1126 ms.mime.unwrap_image(),
1127 &mut ms.reader,
1128 ms.skip_by_seek,
1129 )
1130 .await
1131 }
1132 }
1133 .await;
1134 self.reset();
1135 res
1136 }
1137
1138 pub async fn parse_image_metadata_async<R: AsyncRead + Unpin + Send>(
1139 &mut self,
1140 mut ms: AsyncMediaSource<R>,
1141 ) -> crate::Result<crate::image_metadata::ImageMetadata<crate::ExifIter>> {
1142 self.reset();
1143 let res: crate::Result<crate::image_metadata::ImageMetadata<crate::ExifIter>> = async {
1144 let mime_img = match ms.mime {
1145 crate::file::MediaMime::Image(img) => img,
1146 crate::file::MediaMime::Track(_) => return Err(crate::Error::ExifNotFound),
1147 };
1148
1149 if let Some(memory) = ms.memory.take() {
1150 self.state.set_memory(memory);
1151 } else {
1152 self.acquire_buf();
1153 self.buf_mut().append(&mut ms.buf);
1154 let is_png = matches!(
1160 mime_img,
1161 crate::file::MediaMimeImage::Png | crate::file::MediaMimeImage::Webp
1162 );
1163 match <Self as AsyncBufParser>::fill_buf(self, &mut ms.reader, INIT_BUF_SIZE)
1164 .await
1165 {
1166 Ok(_) => {}
1167 Err(e)
1168 if is_png
1169 && !self.buffer().is_empty()
1170 && e.kind() == io::ErrorKind::UnexpectedEof => {}
1171 Err(e) => return Err(e.into()),
1172 }
1173 }
1174
1175 if mime_img == crate::file::MediaMimeImage::Png {
1176 let (exif, text_chunks) =
1177 crate::exif::parse_png_full_async(self, &mut ms.reader, ms.skip_by_seek)
1178 .await?;
1179 let format = if text_chunks.is_empty() {
1180 None
1181 } else {
1182 Some(crate::ImageFormatMetadata::Png(crate::PngTextChunks {
1183 entries: text_chunks,
1184 }))
1185 };
1186 if exif.is_none() && format.is_none() {
1187 return Err(crate::Error::ExifNotFound);
1188 }
1189 Ok(crate::image_metadata::ImageMetadata { exif, format })
1190 } else {
1191 let iter = crate::exif::parse_exif_iter_async(
1192 self,
1193 mime_img,
1194 &mut ms.reader,
1195 ms.skip_by_seek,
1196 )
1197 .await?;
1198 Ok(crate::image_metadata::ImageMetadata {
1199 exif: Some(iter),
1200 format: None,
1201 })
1202 }
1203 }
1204 .await;
1205 self.reset();
1206 res
1207 }
1208
1209 pub async fn parse_track_async<R: AsyncRead + Unpin + Send>(
1215 &mut self,
1216 mut ms: AsyncMediaSource<R>,
1217 ) -> crate::Result<TrackInfo> {
1218 self.reset();
1219 let res: crate::Result<TrackInfo> = async {
1220 let mime_track = match ms.mime {
1221 crate::file::MediaMime::Image(_) => return Err(crate::Error::TrackNotFound),
1222 crate::file::MediaMime::Track(t) => t,
1223 };
1224 if let Some(memory) = ms.memory.take() {
1225 self.state.set_memory(memory);
1226 } else {
1227 self.acquire_buf();
1228 self.buf_mut().append(&mut ms.buf);
1229 <Self as AsyncBufParser>::fill_buf(self, &mut ms.reader, INIT_BUF_SIZE).await?;
1230 }
1231 let skip = ms.skip_by_seek;
1232 let out = <Self as AsyncBufParser>::load_and_parse(
1233 self,
1234 &mut ms.reader,
1235 skip,
1236 |data, _| {
1237 crate::video::parse_track_info(data, mime_track)
1238 .map_err(|e| ParsingErrorState::new(e, None))
1239 },
1240 )
1241 .await?;
1242 Ok(out)
1243 }
1244 .await;
1245 self.reset();
1246 res
1247 }
1248 }
1249}
1250
1251#[cfg(test)]
1252mod tests {
1253 use std::sync::{LazyLock, Mutex, MutexGuard};
1254
1255 use super::*;
1256 use test_case::case;
1257
1258 enum TrackExif {
1259 Track,
1260 Exif,
1261 NoData,
1262 Invalid,
1263 }
1264 use TrackExif::*;
1265
1266 static PARSER: LazyLock<Mutex<MediaParser>> = LazyLock::new(|| Mutex::new(MediaParser::new()));
1267 fn parser() -> MutexGuard<'static, MediaParser> {
1268 PARSER.lock().unwrap()
1269 }
1270
1271 #[case("3gp_640x360.3gp", Track)]
1272 #[case("broken.jpg", Exif)]
1273 #[case("compatible-brands-fail.heic", Invalid)]
1274 #[case("compatible-brands-fail.mov", Invalid)]
1275 #[case("compatible-brands.heic", NoData)]
1276 #[case("compatible-brands.mov", NoData)]
1277 #[case("embedded-in-heic.mov", Track)]
1278 #[case("exif.heic", Exif)]
1279 #[case("exif.jpg", Exif)]
1280 #[case("exif-no-tz.jpg", Exif)]
1281 #[case("fujifilm_x_t1_01.raf.meta", Exif)]
1282 #[case("meta.mov", Track)]
1283 #[case("meta.mp4", Track)]
1284 #[case("mka.mka", Track)]
1285 #[case("mkv_640x360.mkv", Track)]
1286 #[case("exif-one-entry.heic", Exif)]
1287 #[case("no-exif.jpg", NoData)]
1288 #[case("tif.tif", Exif)]
1289 #[case("ramdisk.img", Invalid)]
1290 #[case("webm_480.webm", Track)]
1291 fn parse_media(path: &str, te: TrackExif) {
1292 let mut parser = parser();
1293 let ms = MediaSource::open(Path::new("testdata").join(path));
1294 match te {
1295 Track => {
1296 let ms = ms.unwrap();
1297 assert_eq!(ms.kind(), MediaKind::Track);
1298 let _: TrackInfo = parser.parse_track(ms).unwrap();
1299 }
1300 Exif => {
1301 let ms = ms.unwrap();
1302 assert_eq!(ms.kind(), MediaKind::Image);
1303 let mut it: ExifIter = parser.parse_exif(ms).unwrap();
1304 let _ = it.parse_gps();
1305
1306 if path.contains("one-entry") {
1307 assert!(it.next().is_some());
1308 assert!(it.next().is_none());
1309
1310 let exif: crate::Exif = it.clone_rewound().into();
1311 assert!(exif.get(ExifTag::Orientation).is_some());
1312 } else {
1313 let _: crate::Exif = it.clone_rewound().into();
1314 }
1315 }
1316 NoData => {
1317 let ms = ms.unwrap();
1318 match ms.kind() {
1319 MediaKind::Image => {
1320 let res = parser.parse_exif(ms);
1321 res.unwrap_err();
1322 }
1323 MediaKind::Track => {
1324 let res = parser.parse_track(ms);
1325 res.unwrap_err();
1326 }
1327 }
1328 }
1329 Invalid => {
1330 ms.unwrap_err();
1331 }
1332 }
1333 }
1334
1335 use crate::testkit::open_sample;
1336 use crate::{EntryValue, Exif, ExifTag, TrackInfoTag};
1337 use chrono::{DateTime, FixedOffset, NaiveDateTime};
1338 use test_case::test_case;
1339
1340 #[test_case("exif.jpg", ExifTag::DateTimeOriginal, DateTime::parse_from_str("2023-07-09T20:36:33+08:00", "%+").unwrap().into())]
1341 #[test_case("exif.heic", ExifTag::DateTimeOriginal, DateTime::parse_from_str("2022-07-22T21:26:32+08:00", "%+").unwrap().into())]
1342 #[test_case("exif.jpg", ExifTag::DateTimeOriginal,
1343 (NaiveDateTime::parse_from_str("2023-07-09T20:36:33", "%Y-%m-%dT%H:%M:%S").unwrap(),
1344 Some(FixedOffset::east_opt(8*3600).unwrap())).into())]
1345 #[test_case("exif-no-tz.jpg", ExifTag::DateTimeOriginal,
1346 (NaiveDateTime::parse_from_str("2023-07-09T20:36:33", "%Y-%m-%dT%H:%M:%S").unwrap(), None).into())]
1347 fn parse_exif(path: &str, tag: ExifTag, v: EntryValue) {
1348 let mut parser = parser();
1349
1350 let mf = MediaSource::seekable(open_sample(path).unwrap()).unwrap();
1351 assert_eq!(mf.kind(), MediaKind::Image);
1352 let iter: ExifIter = parser.parse_exif(mf).unwrap();
1353 let exif: Exif = iter.into();
1354 assert_eq!(exif.get(tag).unwrap(), &v);
1355
1356 let mf = MediaSource::unseekable(open_sample(path).unwrap()).unwrap();
1357 assert_eq!(mf.kind(), MediaKind::Image);
1358 let iter: ExifIter = parser.parse_exif(mf).unwrap();
1359 let exif: Exif = iter.into();
1360 assert_eq!(exif.get(tag).unwrap(), &v);
1361 }
1362
1363 use crate::video::TrackInfoTag::*;
1364
1365 #[test_case("mkv_640x360.mkv", Width, 640_u32.into())]
1366 #[test_case("mkv_640x360.mkv", Height, 360_u32.into())]
1367 #[test_case("mkv_640x360.mkv", DurationMs, 13346_u64.into())]
1368 #[test_case("mkv_640x360.mkv", CreateDate, DateTime::parse_from_str("2008-08-08T08:08:08Z", "%+").unwrap().into())]
1369 #[test_case("meta.mov", Make, "Apple".into())]
1370 #[test_case("meta.mov", Model, "iPhone X".into())]
1371 #[test_case("meta.mov", GpsIso6709, "+27.1281+100.2508+000.000/".into())]
1372 #[test_case("meta.mov", CreateDate, DateTime::parse_from_str("2019-02-12T15:27:12+08:00", "%+").unwrap().into())]
1373 #[test_case("meta.mp4", Width, 1920_u32.into())]
1374 #[test_case("meta.mp4", Height, 1080_u32.into())]
1375 #[test_case("meta.mp4", DurationMs, 1063_u64.into())]
1376 #[test_case("meta.mp4", GpsIso6709, "+27.2939+112.6932/".into())]
1377 #[test_case("meta.mp4", CreateDate, DateTime::parse_from_str("2024-02-03T07:05:38Z", "%+").unwrap().into())]
1378 #[test_case("udta.auth.mp4", Author, "ReplayKitRecording".into(); "udta author")]
1379 #[test_case("auth.mov", Author, "ReplayKitRecording".into(); "mov author")]
1380 #[test_case("sony-a7-xavc.MP4", Width, 1920_u32.into())]
1381 #[test_case("sony-a7-xavc.MP4", Height, 1080_u32.into())]
1382 #[test_case("sony-a7-xavc.MP4", DurationMs, 1440_u64.into())]
1383 #[test_case("sony-a7-xavc.MP4", CreateDate, DateTime::parse_from_str("2026-04-26T09:25:15+00:00", "%+").unwrap().into())]
1384 fn parse_track_info(path: &str, tag: TrackInfoTag, v: EntryValue) {
1385 let mut parser = parser();
1386
1387 let mf = MediaSource::seekable(open_sample(path).unwrap()).unwrap();
1388 let info: TrackInfo = parser.parse_track(mf).unwrap();
1389 assert_eq!(info.get(tag).unwrap(), &v);
1390
1391 let mf = MediaSource::unseekable(open_sample(path).unwrap()).unwrap();
1392 let info: TrackInfo = parser.parse_track(mf).unwrap();
1393 assert_eq!(info.get(tag).unwrap(), &v);
1394 }
1395
1396 #[test_case("crash_moov-trak")]
1397 #[test_case("crash_skip_large")]
1398 #[test_case("crash_add_large")]
1399 fn parse_track_crash(path: &str) {
1400 let mut parser = parser();
1401
1402 let mf = MediaSource::seekable(open_sample(path).unwrap()).unwrap();
1403 let _: TrackInfo = parser.parse_track(mf).unwrap_or_default();
1404
1405 let mf = MediaSource::unseekable(open_sample(path).unwrap()).unwrap();
1406 let _: TrackInfo = parser.parse_track(mf).unwrap_or_default();
1407 }
1408
1409 #[test]
1414 fn parse_oom_large_box() {
1415 let mut parser = parser();
1416
1417 let mf = MediaSource::seekable(open_sample("oom_large_box.heic").unwrap()).unwrap();
1418 let _: Result<ExifIter, _> = parser.parse_exif(mf);
1419
1420 let mf = MediaSource::unseekable(open_sample("oom_large_box.heic").unwrap()).unwrap();
1421 let _: Result<ExifIter, _> = parser.parse_exif(mf);
1422
1423 let mf = MediaSource::seekable(open_sample("oom_large_box.heic").unwrap()).unwrap();
1424 let _: TrackInfo = parser.parse_track(mf).unwrap_or_default();
1425
1426 let mf = MediaSource::unseekable(open_sample("oom_large_box.heic").unwrap()).unwrap();
1427 let _: TrackInfo = parser.parse_track(mf).unwrap_or_default();
1428 }
1429
1430 #[test]
1431 fn media_kind_classifies_image_and_track() {
1432 let img = MediaSource::open("testdata/exif.jpg").unwrap();
1433 assert_eq!(img.kind(), MediaKind::Image);
1434
1435 let trk = MediaSource::open("testdata/meta.mov").unwrap();
1436 assert_eq!(trk.kind(), MediaKind::Track);
1437 }
1438
1439 #[test]
1440 fn media_source_open() {
1441 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1442 assert_eq!(ms.kind(), MediaKind::Image);
1443 }
1444
1445 #[test]
1446 fn parse_exif_returns_exif_iter() {
1447 let mut parser = parser();
1448 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1449 let _: ExifIter = parser.parse_exif(ms).unwrap();
1450 }
1451
1452 #[test]
1453 fn parse_track_returns_track_info() {
1454 let mut parser = parser();
1455 let ms = MediaSource::open("testdata/meta.mov").unwrap();
1456 let _: TrackInfo = parser.parse_track(ms).unwrap();
1457 }
1458
1459 #[test]
1460 fn parse_exif_on_track_returns_exif_not_found_v3() {
1461 let mut parser = parser();
1462 let ms = MediaSource::open("testdata/meta.mov").unwrap();
1463 let res = parser.parse_exif(ms);
1464 assert!(matches!(res, Err(crate::Error::ExifNotFound)));
1465 }
1466
1467 #[test]
1468 fn parse_track_on_image_returns_track_not_found_v3() {
1469 let mut parser = parser();
1470 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1471 let res = parser.parse_track(ms);
1472 assert!(matches!(res, Err(crate::Error::TrackNotFound)));
1473 }
1474
1475 #[cfg(feature = "tokio-fs")]
1476 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1477 async fn media_parser_parse_exif_async() {
1478 use crate::parser_async::AsyncMediaSource;
1479 let mut parser = MediaParser::new();
1480 let ms = AsyncMediaSource::open("testdata/exif.jpg").await.unwrap();
1481 let _: ExifIter = parser.parse_exif_async(ms).await.unwrap();
1482 }
1483
1484 #[cfg(feature = "tokio-fs")]
1485 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1486 async fn media_parser_parse_track_async() {
1487 use crate::parser_async::AsyncMediaSource;
1488 let mut parser = MediaParser::new();
1489 let ms = AsyncMediaSource::open("testdata/meta.mov").await.unwrap();
1490 let _: TrackInfo = parser.parse_track_async(ms).await.unwrap();
1491 }
1492
1493 #[test]
1494 fn parser_recycles_alloc_when_exif_iter_dropped() {
1495 let mut parser = MediaParser::new();
1496
1497 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1498 let iter = parser.parse_exif(ms).unwrap();
1499 let exif: crate::Exif = iter.into();
1500 drop(exif);
1501 let ptr_after_first = parser.state.cached_ptr_for_test();
1502
1503 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1504 let iter = parser.parse_exif(ms).unwrap();
1505 let _exif: crate::Exif = iter.into();
1506 let ptr_after_second = parser.state.cached_ptr_for_test();
1507
1508 assert!(
1509 ptr_after_first.is_some() && ptr_after_first == ptr_after_second,
1510 "expected recycled allocation, got {:?} -> {:?}",
1511 ptr_after_first,
1512 ptr_after_second
1513 );
1514 }
1515
1516 #[test]
1517 fn parser_new_does_no_upfront_allocation() {
1518 let parser = MediaParser::new();
1519 assert!(parser.state.cached_ptr_for_test().is_none());
1520 assert!(parser.state.buf_is_none_for_test());
1521 }
1522
1523 #[test]
1524 fn buffered_state_memory_mode_sets_and_reads() {
1525 let mut s = BufferedParserState::new();
1526 s.set_memory(bytes::Bytes::from_static(b"abcdefgh"));
1527 assert!(s.is_memory_mode());
1528 assert_eq!(s.buffer(), b"abcdefgh");
1529 s.set_position(3);
1530 assert_eq!(s.buffer(), b"defgh");
1531 }
1532
1533 #[test]
1534 fn buffered_state_share_buf_memory_mode_is_zero_copy() {
1535 let original = bytes::Bytes::from_static(b"the parser owns nothing here");
1536 let original_ptr = original.as_ptr();
1537 let mut s = BufferedParserState::new();
1538 s.set_memory(original);
1539 let (shared, position) = s.share_buf();
1540 assert_eq!(position, 0);
1541 assert_eq!(
1542 shared.as_ptr(),
1543 original_ptr,
1544 "memory share must be a Bytes::clone, not a Vec round-trip"
1545 );
1546 assert!(!s.is_memory_mode());
1549 }
1550
1551 #[test]
1552 fn buffered_state_reset_clears_memory() {
1553 let mut s = BufferedParserState::new();
1554 s.set_memory(bytes::Bytes::from_static(b"x"));
1555 s.reset();
1556 assert!(!s.is_memory_mode());
1557 assert_eq!(s.position, 0);
1558 }
1559
1560 #[test]
1561 fn buffered_state_acquire_buf_skips_in_memory_mode() {
1562 let mut s = BufferedParserState::new();
1563 s.set_memory(bytes::Bytes::from_static(b"data"));
1564 s.acquire_buf();
1565 assert!(s.buf.is_none());
1567 assert_eq!(s.buffer(), b"data");
1569 }
1570
1571 #[test]
1572 fn media_source_from_memory_image_jpg() {
1573 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1574 let ms = MediaSource::from_memory(raw).unwrap();
1575 assert_eq!(ms.kind(), MediaKind::Image);
1576 assert!(ms.memory.is_some());
1577 }
1578
1579 #[test]
1580 fn media_source_from_memory_track_mov() {
1581 let raw = std::fs::read("testdata/meta.mov").unwrap();
1582 let ms = MediaSource::from_memory(raw).unwrap();
1583 assert_eq!(ms.kind(), MediaKind::Track);
1584 }
1585
1586 #[test]
1587 fn media_source_from_memory_static_slice() {
1588 let raw: &'static [u8] = include_bytes!("../testdata/exif.jpg");
1589 let ms = MediaSource::from_memory(raw).unwrap();
1590 assert_eq!(ms.kind(), MediaKind::Image);
1591 }
1592
1593 #[test]
1594 fn media_source_from_memory_rejects_too_short() {
1595 let raw = vec![0u8; 4];
1596 let res = MediaSource::from_memory(raw);
1597 assert!(res.is_err());
1598 }
1599
1600 #[test]
1601 fn media_source_from_memory_rejects_unknown_mime() {
1602 let raw = vec![0xAAu8; 256];
1603 let res = MediaSource::from_memory(raw);
1604 assert!(res.is_err());
1605 }
1606
1607 #[test]
1608 fn parse_exif_unified_from_memory_jpg() {
1609 let mut parser = MediaParser::new();
1610 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1611 let ms = MediaSource::from_memory(raw).unwrap();
1612 let iter = parser.parse_exif(ms).unwrap();
1613 let exif: crate::Exif = iter.into();
1614 assert!(exif.get(crate::ExifTag::Make).is_some());
1615 }
1616
1617 #[test]
1618 fn parse_exif_unified_from_memory_heic() {
1619 let mut parser = MediaParser::new();
1620 let raw = std::fs::read("testdata/exif.heic").unwrap();
1621 let ms = MediaSource::from_memory(raw).unwrap();
1622 let iter = parser.parse_exif(ms).unwrap();
1623 let exif: crate::Exif = iter.into();
1624 assert_eq!(
1625 exif.get(crate::ExifTag::Make).and_then(|v| v.as_str()),
1626 Some("Apple")
1627 );
1628 }
1629
1630 #[test]
1631 fn parse_exif_unified_from_memory_zero_copy_preserved() {
1632 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1633 let bytes = bytes::Bytes::from(raw);
1634 let _original_ptr = bytes.as_ptr();
1635
1636 let mut parser = MediaParser::new();
1637 let ms = MediaSource::from_memory(bytes).unwrap();
1638 let iter = parser.parse_exif(ms).unwrap();
1639
1640 assert!(
1643 parser.state.cached_ptr_for_test().is_none(),
1644 "memory mode must not write to the streaming-buf recycle cache"
1645 );
1646 drop(iter);
1647 }
1648
1649 #[test]
1650 fn parse_exif_unified_on_track_returns_exif_not_found() {
1651 let mut parser = MediaParser::new();
1652 let raw = std::fs::read("testdata/meta.mov").unwrap();
1653 let ms = MediaSource::from_memory(raw).unwrap();
1654 let res = parser.parse_exif(ms);
1655 assert!(matches!(res, Err(crate::Error::ExifNotFound)));
1656 }
1657
1658 #[test]
1659 fn parse_exif_unified_on_truncated_returns_io_error() {
1660 let mut raw = std::fs::read("testdata/exif.jpg").unwrap();
1661 raw.truncate(200);
1662 let mut parser = MediaParser::new();
1663 let ms = MediaSource::from_memory(raw).unwrap();
1664 let res = parser.parse_exif(ms);
1665 assert!(
1666 res.is_err(),
1667 "expected error on truncated bytes, got {:?}",
1668 res
1669 );
1670 }
1671
1672 #[test]
1673 fn parse_exif_streaming_tiny_non_png_keeps_strict_eof() {
1674 let mut parser = MediaParser::new();
1680 let ms = MediaSource::open("testdata/compatible-brands.heic").unwrap();
1681 let res = parser.parse_exif(ms);
1682 assert!(res.is_err(), "expected Err for tiny HEIC, got {:?}", res);
1683 }
1684
1685 #[test]
1686 #[allow(deprecated)]
1687 fn media_source_from_bytes_image_jpg() {
1688 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1689 let ms = MediaSource::from_bytes(raw).unwrap();
1690 assert_eq!(ms.kind(), MediaKind::Image);
1691 assert!(ms.memory.is_some());
1692 }
1693
1694 #[test]
1695 #[allow(deprecated)]
1696 fn media_source_from_bytes_track_mov() {
1697 let raw = std::fs::read("testdata/meta.mov").unwrap();
1698 let ms = MediaSource::from_bytes(raw).unwrap();
1699 assert_eq!(ms.kind(), MediaKind::Track);
1700 }
1701
1702 #[test]
1703 #[allow(deprecated)]
1704 fn media_source_from_bytes_static_slice() {
1705 let raw: &'static [u8] = include_bytes!("../testdata/exif.jpg");
1708 let ms = MediaSource::from_bytes(raw).unwrap();
1709 assert_eq!(ms.kind(), MediaKind::Image);
1710 }
1711
1712 #[test]
1713 #[allow(deprecated)]
1714 fn media_source_from_bytes_rejects_too_short() {
1715 let raw = vec![0u8; 4];
1717 let res = MediaSource::from_bytes(raw);
1718 assert!(res.is_err(), "expected mime-detection error");
1719 }
1720
1721 #[test]
1722 #[allow(deprecated)]
1723 fn media_source_from_bytes_rejects_unknown_mime() {
1724 let raw = vec![0xAAu8; 256];
1727 let res = MediaSource::from_bytes(raw);
1728 assert!(
1729 res.is_err(),
1730 "expected mime-detection error for unknown bytes"
1731 );
1732 }
1733
1734 #[test]
1735 fn p4_5_baseline_exif_jpg_full_dump() {
1736 let mut parser = MediaParser::new();
1741 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1742 let iter: ExifIter = parser.parse_exif(ms).unwrap();
1743
1744 let mut entries: Vec<String> = iter
1745 .map(|e| {
1746 let tag_name = match e.tag() {
1747 crate::TagOrCode::Tag(t) => format!("{t}"),
1748 crate::TagOrCode::Unknown(c) => format!("0x{c:04x}"),
1749 };
1750 let value_str = e
1751 .value()
1752 .map(|v| format!("{v}"))
1753 .unwrap_or_else(|| "<err>".into());
1754 format!("{}.{}={:?}", e.ifd(), tag_name, value_str)
1755 })
1756 .collect();
1757 entries.sort();
1758 let snapshot = entries.join("\n");
1759
1760 assert!(
1764 entries.len() > 5,
1765 "expected >5 entries, got {}",
1766 entries.len()
1767 );
1768 assert!(snapshot.contains("Make"), "expected Make tag in snapshot");
1769 }
1770
1771 #[test]
1772 #[allow(deprecated)]
1773 fn parse_exif_from_bytes_jpg_basic() {
1774 let mut parser = MediaParser::new();
1775 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1776 let ms = MediaSource::from_bytes(raw).unwrap();
1777 let iter = parser.parse_exif_from_bytes(ms).unwrap();
1778 let exif: crate::Exif = iter.into();
1779 assert!(exif.get(crate::ExifTag::Make).is_some());
1780 }
1781
1782 #[test]
1783 #[allow(deprecated)]
1784 fn parse_exif_from_bytes_heic_basic() {
1785 let mut parser = MediaParser::new();
1786 let raw = std::fs::read("testdata/exif.heic").unwrap();
1787 let ms = MediaSource::from_bytes(raw).unwrap();
1788 let iter = parser.parse_exif_from_bytes(ms).unwrap();
1789 let exif: crate::Exif = iter.into();
1790 assert_eq!(
1791 exif.get(crate::ExifTag::Make).and_then(|v| v.as_str()),
1792 Some("Apple")
1793 );
1794 }
1795
1796 #[test]
1797 #[allow(deprecated)]
1798 fn parse_exif_from_bytes_zero_copy_shared_bytes() {
1799 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1802 let bytes = bytes::Bytes::from(raw);
1803 let original_ptr = bytes.as_ptr();
1804
1805 let mut parser = MediaParser::new();
1806 let ms = MediaSource::from_bytes(bytes).unwrap();
1807 let iter = parser.parse_exif_from_bytes(ms).unwrap();
1808
1809 assert!(
1812 parser.state.cached_ptr_for_test().is_none(),
1813 "memory mode must not poison the recycle cache"
1814 );
1815
1816 drop(iter);
1818
1819 let bytes2 = bytes::Bytes::from(std::fs::read("testdata/exif.jpg").unwrap());
1822 let ms2 = MediaSource::from_bytes(bytes2.clone()).unwrap();
1823 let _iter2 = parser.parse_exif_from_bytes(ms2).unwrap();
1824 let _ = original_ptr; }
1828
1829 #[test]
1830 #[allow(deprecated)]
1831 fn parse_exif_from_bytes_on_track_returns_exif_not_found() {
1832 let mut parser = MediaParser::new();
1833 let raw = std::fs::read("testdata/meta.mov").unwrap();
1834 let ms = MediaSource::from_bytes(raw).unwrap();
1835 let res = parser.parse_exif_from_bytes(ms);
1836 assert!(matches!(res, Err(crate::Error::ExifNotFound)));
1837 }
1838
1839 #[test]
1840 #[allow(deprecated)]
1841 fn parse_exif_from_bytes_on_truncated_returns_io_error() {
1842 let mut raw = std::fs::read("testdata/exif.jpg").unwrap();
1846 raw.truncate(200);
1847 let mut parser = MediaParser::new();
1848 let ms = MediaSource::from_bytes(raw).unwrap();
1849 let res = parser.parse_exif_from_bytes(ms);
1850 assert!(
1851 res.is_err(),
1852 "expected error on truncated bytes, got {:?}",
1853 res
1854 );
1855 }
1856
1857 #[test]
1858 #[allow(deprecated)]
1859 fn parse_track_from_bytes_mov_basic() {
1860 let mut parser = MediaParser::new();
1861 let raw = std::fs::read("testdata/meta.mov").unwrap();
1862 let ms = MediaSource::from_bytes(raw).unwrap();
1863 let info = parser.parse_track_from_bytes(ms).unwrap();
1864 assert_eq!(info.get(crate::TrackInfoTag::Make), Some(&"Apple".into()));
1865 assert_eq!(
1866 info.get(crate::TrackInfoTag::Model),
1867 Some(&"iPhone X".into())
1868 );
1869 }
1870
1871 #[test]
1872 #[allow(deprecated)]
1873 fn parse_track_from_bytes_mp4_basic() {
1874 let mut parser = MediaParser::new();
1875 let raw = std::fs::read("testdata/meta.mp4").unwrap();
1876 let ms = MediaSource::from_bytes(raw).unwrap();
1877 let info = parser.parse_track_from_bytes(ms).unwrap();
1878 assert!(info.get(crate::TrackInfoTag::CreateDate).is_some());
1879 }
1880
1881 #[test]
1882 #[allow(deprecated)]
1883 fn parse_track_from_bytes_mkv_basic() {
1884 let mut parser = MediaParser::new();
1885 let raw = std::fs::read("testdata/mkv_640x360.mkv").unwrap();
1886 let ms = MediaSource::from_bytes(raw).unwrap();
1887 let info = parser.parse_track_from_bytes(ms).unwrap();
1888 assert_eq!(
1889 info.get(crate::TrackInfoTag::Width),
1890 Some(&(640_u32.into()))
1891 );
1892 }
1893
1894 #[test]
1895 #[allow(deprecated)]
1896 fn parse_track_from_bytes_on_image_returns_track_not_found() {
1897 let mut parser = MediaParser::new();
1898 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1899 let ms = MediaSource::from_bytes(raw).unwrap();
1900 let res = parser.parse_track_from_bytes(ms);
1901 assert!(matches!(res, Err(crate::Error::TrackNotFound)));
1902 }
1903
1904 #[test]
1905 fn parse_track_unified_from_memory_mov() {
1906 let mut parser = MediaParser::new();
1907 let raw = std::fs::read("testdata/meta.mov").unwrap();
1908 let ms = MediaSource::from_memory(raw).unwrap();
1909 let info = parser.parse_track(ms).unwrap();
1910 assert_eq!(info.get(crate::TrackInfoTag::Make), Some(&"Apple".into()));
1911 }
1912
1913 #[test]
1914 fn parse_track_unified_from_memory_mp4() {
1915 let mut parser = MediaParser::new();
1916 let raw = std::fs::read("testdata/meta.mp4").unwrap();
1917 let ms = MediaSource::from_memory(raw).unwrap();
1918 let info = parser.parse_track(ms).unwrap();
1919 assert!(info.get(crate::TrackInfoTag::CreateDate).is_some());
1920 }
1921
1922 #[test]
1923 fn parse_track_unified_on_image_returns_track_not_found() {
1924 let mut parser = MediaParser::new();
1925 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1926 let ms = MediaSource::from_memory(raw).unwrap();
1927 let res = parser.parse_track(ms);
1928 assert!(matches!(res, Err(crate::Error::TrackNotFound)));
1929 }
1930
1931 #[test]
1932 fn parse_image_metadata_jpeg_returns_exif_only() {
1933 let mut parser = MediaParser::new();
1934 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1935 let img = parser.parse_image_metadata(ms).unwrap();
1936 assert!(img.exif.is_some());
1937 assert!(img.format.is_none());
1938 }
1939
1940 #[test]
1941 fn parse_image_metadata_jpeg_from_memory() {
1942 let mut parser = MediaParser::new();
1943 let raw = std::fs::read("testdata/exif.jpg").unwrap();
1944 let ms = MediaSource::from_memory(raw).unwrap();
1945 let img = parser.parse_image_metadata(ms).unwrap();
1946 assert!(img.exif.is_some());
1947 assert!(img.format.is_none());
1948 }
1949
1950 #[test]
1951 fn parse_image_metadata_on_track_returns_exif_not_found() {
1952 let mut parser = MediaParser::new();
1953 let ms = MediaSource::open("testdata/meta.mov").unwrap();
1954 let res = parser.parse_image_metadata(ms);
1955 assert!(matches!(res, Err(crate::Error::ExifNotFound)));
1956 }
1957
1958 #[test]
1963 fn parse_image_metadata_png_large_idat_streaming() {
1964 use std::io::Cursor;
1965 let png = build_png_with_large_idat(INIT_BUF_SIZE + 1024, false);
1966 let mut parser = MediaParser::new();
1967 let ms = MediaSource::seekable(Cursor::new(png)).unwrap();
1968 assert_eq!(ms.kind(), crate::MediaKind::Image);
1969 let res = parser.parse_image_metadata(ms);
1970 assert!(
1971 matches!(res, Err(crate::Error::ExifNotFound)),
1972 "expected ExifNotFound on PNG with no EXIF / tEXt; got {res:?}"
1973 );
1974 }
1975
1976 #[test]
1979 fn parse_exif_png_large_idat_streaming() {
1980 use std::io::Cursor;
1981 let png = build_png_with_large_idat(INIT_BUF_SIZE + 1024, false);
1982 let mut parser = MediaParser::new();
1983 let ms = MediaSource::seekable(Cursor::new(png)).unwrap();
1984 let res = parser.parse_exif(ms);
1985 assert!(
1986 matches!(res, Err(crate::Error::ExifNotFound)),
1987 "expected ExifNotFound; got {res:?}"
1988 );
1989 }
1990
1991 #[test]
1995 fn parse_image_metadata_png_large_idat_unseekable() {
1996 let png = build_png_with_large_idat(INIT_BUF_SIZE + 1024, false);
1997 let mut parser = MediaParser::new();
1998 let ms = MediaSource::unseekable(NoSeek(std::io::Cursor::new(png))).unwrap();
1999 let res = parser.parse_image_metadata(ms);
2000 assert!(
2001 matches!(res, Err(crate::Error::ExifNotFound)),
2002 "expected ExifNotFound; got {res:?}"
2003 );
2004 }
2005
2006 struct NoSeek<R>(R);
2009 impl<R: io::Read> io::Read for NoSeek<R> {
2010 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
2011 self.0.read(buf)
2012 }
2013 }
2014
2015 #[test]
2017 fn parse_image_metadata_png_exif_before_large_idat() {
2018 use std::io::Cursor;
2019 let png = build_png_with_large_idat(INIT_BUF_SIZE + 1024, true);
2020 let mut parser = MediaParser::new();
2021 let ms = MediaSource::seekable(Cursor::new(png)).unwrap();
2022 let image = parser.parse_image_metadata(ms).unwrap();
2023 assert!(image.exif.is_some());
2024 let Some(crate::ImageFormatMetadata::Png(text)) = image.format else {
2025 panic!("missing PNG text metadata");
2026 };
2027 assert_eq!(text.get("Title"), Some("before IDAT"));
2028 }
2029
2030 fn build_png_with_large_idat(idat_body: usize, with_metadata: bool) -> Vec<u8> {
2031 let mut out = Vec::new();
2032 out.extend_from_slice(b"\x89PNG\r\n\x1a\n");
2033 out.extend_from_slice(&13u32.to_be_bytes());
2035 out.extend_from_slice(b"IHDR");
2036 out.extend_from_slice(&[0, 0, 0, 1, 0, 0, 0, 1, 8, 0, 0, 0, 0]);
2037 out.extend_from_slice(&[0, 0, 0, 0]); if with_metadata {
2039 let text = b"Title\0before IDAT";
2040 out.extend_from_slice(&(text.len() as u32).to_be_bytes());
2041 out.extend_from_slice(b"tEXt");
2042 out.extend_from_slice(text);
2043 out.extend_from_slice(&[0, 0, 0, 0]);
2044 let tiff = b"II\x2a\0\x08\0\0\0\0\0\0\0\0\0";
2045 out.extend_from_slice(&(tiff.len() as u32).to_be_bytes());
2046 out.extend_from_slice(b"eXIf");
2047 out.extend_from_slice(tiff);
2048 out.extend_from_slice(&[0, 0, 0, 0]);
2049 }
2050 out.extend_from_slice(&(idat_body as u32).to_be_bytes());
2052 out.extend_from_slice(b"IDAT");
2053 out.resize(out.len() + idat_body, 0);
2054 out.extend_from_slice(&[0, 0, 0, 0]); out.extend_from_slice(&0u32.to_be_bytes());
2057 out.extend_from_slice(b"IEND");
2058 out.extend_from_slice(&[0, 0, 0, 0]); out
2060 }
2061
2062 #[cfg(feature = "tokio-fs")]
2063 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2064 async fn parse_image_metadata_async_jpeg() {
2065 use crate::parser_async::AsyncMediaSource;
2066 let mut parser = MediaParser::new();
2067 let ms = AsyncMediaSource::open("testdata/exif.jpg").await.unwrap();
2068 let img = parser.parse_image_metadata_async(ms).await.unwrap();
2069 assert!(img.exif.is_some());
2070 assert!(img.format.is_none());
2071 }
2072
2073 #[cfg(feature = "tokio-fs")]
2074 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2075 async fn async_media_source_from_memory_image_jpg() {
2076 use crate::parser_async::AsyncMediaSource;
2077 let raw = std::fs::read("testdata/exif.jpg").unwrap();
2078 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2079 assert_eq!(ms.kind(), MediaKind::Image);
2080 assert!(ms.memory.is_some());
2081 }
2082
2083 #[cfg(feature = "tokio-fs")]
2084 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2085 async fn async_media_source_from_memory_track_mov() {
2086 use crate::parser_async::AsyncMediaSource;
2087 let raw = std::fs::read("testdata/meta.mov").unwrap();
2088 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2089 assert_eq!(ms.kind(), MediaKind::Track);
2090 }
2091
2092 #[cfg(feature = "tokio-fs")]
2093 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2094 async fn async_media_source_from_memory_rejects_unknown_mime() {
2095 use crate::parser_async::AsyncMediaSource;
2096 let raw = vec![0xAAu8; 256];
2097 let res = AsyncMediaSource::from_memory(raw);
2098 assert!(res.is_err());
2099 }
2100
2101 #[cfg(feature = "tokio-fs")]
2102 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2103 async fn parse_exif_async_from_memory_jpg() {
2104 use crate::parser_async::AsyncMediaSource;
2105 let raw = std::fs::read("testdata/exif.jpg").unwrap();
2106 let mut parser = MediaParser::new();
2107 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2108 let iter = parser.parse_exif_async(ms).await.unwrap();
2109 let exif: crate::Exif = iter.into();
2110 assert!(exif.get(crate::ExifTag::Make).is_some());
2111 }
2112
2113 #[cfg(feature = "tokio-fs")]
2114 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2115 async fn parse_exif_async_from_memory_zero_copy_preserved() {
2116 use crate::parser_async::AsyncMediaSource;
2117 let raw = std::fs::read("testdata/exif.jpg").unwrap();
2118 let bytes = bytes::Bytes::from(raw);
2119 let mut parser = MediaParser::new();
2120 let ms = AsyncMediaSource::from_memory(bytes).unwrap();
2121 let iter = parser.parse_exif_async(ms).await.unwrap();
2122 assert!(
2125 parser.state.cached_ptr_for_test().is_none(),
2126 "async memory mode must not write to the streaming-buf recycle cache"
2127 );
2128 drop(iter);
2129 }
2130
2131 #[cfg(feature = "tokio-fs")]
2132 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2133 async fn parse_track_async_from_memory_mov() {
2134 use crate::parser_async::AsyncMediaSource;
2135 let raw = std::fs::read("testdata/meta.mov").unwrap();
2136 let mut parser = MediaParser::new();
2137 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2138 let info = parser.parse_track_async(ms).await.unwrap();
2139 assert_eq!(info.get(crate::TrackInfoTag::Make), Some(&"Apple".into()));
2140 }
2141
2142 #[cfg(feature = "tokio-fs")]
2143 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2144 async fn parse_image_metadata_async_from_memory_png() {
2145 use crate::parser_async::AsyncMediaSource;
2146 let raw = std::fs::read("testdata/exif.png").unwrap();
2147 let mut parser = MediaParser::new();
2148 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2149 let img = parser.parse_image_metadata_async(ms).await.unwrap();
2150 assert!(img.exif.is_some());
2151 assert!(img.format.is_some());
2152 }
2153
2154 #[cfg(feature = "tokio-fs")]
2159 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2160 async fn parse_image_metadata_async_png_large_idat_streaming() {
2161 use crate::parser_async::AsyncMediaSource;
2162 let png = build_png_with_large_idat(INIT_BUF_SIZE + 1024, false);
2163 let path =
2167 std::env::temp_dir().join(format!("nom-exif-issue55-{}.png", std::process::id()));
2168 tokio::fs::write(&path, &png).await.unwrap();
2169 let mut parser = MediaParser::new();
2170 let ms = AsyncMediaSource::open(&path).await.unwrap();
2171 let res = parser.parse_image_metadata_async(ms).await;
2172 let _ = tokio::fs::remove_file(&path).await;
2173 assert!(
2174 matches!(res, Err(crate::Error::ExifNotFound)),
2175 "expected ExifNotFound; got {res:?}"
2176 );
2177 }
2178
2179 #[cfg(feature = "tokio")]
2180 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2181 async fn parse_image_metadata_async_from_memory_text_only_png() {
2182 use crate::parser_async::AsyncMediaSource;
2186 let raw = std::fs::read("testdata/text-only.png").unwrap();
2187 let mut parser = MediaParser::new();
2188 let ms = AsyncMediaSource::from_memory(raw).unwrap();
2189 let img = parser.parse_image_metadata_async(ms).await.unwrap();
2190 assert!(img.exif.is_none());
2191 assert!(img.format.is_some());
2192 }
2193
2194 #[test]
2203 fn parse_exif_streaming_text_only_png_returns_exif_not_found() {
2204 let mut parser = MediaParser::new();
2208 let ms = MediaSource::open("testdata/text-only.png").unwrap();
2209 let res = parser.parse_exif(ms);
2210 assert!(
2211 matches!(res, Err(crate::Error::ExifNotFound)),
2212 "expected ExifNotFound for tEXt-only PNG, got {:?}",
2213 res
2214 );
2215 }
2216
2217 #[test]
2218 fn parse_image_metadata_streaming_text_only_png() {
2219 let mut parser = MediaParser::new();
2220 let ms = MediaSource::open("testdata/text-only.png").unwrap();
2221 let img = parser.parse_image_metadata(ms).unwrap();
2222 assert!(img.exif.is_none());
2223 assert!(img.format.is_some());
2224 }
2225
2226 #[cfg(feature = "tokio")]
2227 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2228 async fn parse_exif_async_streaming_text_only_png_returns_exif_not_found() {
2229 use crate::parser_async::AsyncMediaSource;
2230 let mut parser = MediaParser::new();
2231 let f = tokio::fs::File::open("testdata/text-only.png")
2232 .await
2233 .unwrap();
2234 let ms = AsyncMediaSource::seekable(f).await.unwrap();
2235 let res = parser.parse_exif_async(ms).await;
2236 assert!(
2237 matches!(res, Err(crate::Error::ExifNotFound)),
2238 "expected ExifNotFound for tEXt-only PNG via async streaming, got {:?}",
2239 res
2240 );
2241 }
2242
2243 #[cfg(feature = "tokio")]
2244 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2245 async fn parse_image_metadata_async_streaming_text_only_png() {
2246 use crate::parser_async::AsyncMediaSource;
2247 let mut parser = MediaParser::new();
2248 let f = tokio::fs::File::open("testdata/text-only.png")
2249 .await
2250 .unwrap();
2251 let ms = AsyncMediaSource::seekable(f).await.unwrap();
2252 let img = parser.parse_image_metadata_async(ms).await.unwrap();
2253 assert!(img.exif.is_none());
2254 assert!(img.format.is_some());
2255 }
2256
2257 fn webp_minimal_tiff_le() -> Vec<u8> {
2259 let mut t = Vec::new();
2261 t.extend_from_slice(b"II");
2262 t.extend_from_slice(&[0x2a, 0x00]);
2263 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
2272 }
2273
2274 fn webp_with_exif(tiff: &[u8]) -> Vec<u8> {
2275 let mut exif_chunk = Vec::new();
2276 exif_chunk.extend_from_slice(b"EXIF");
2277 exif_chunk.extend_from_slice(&(tiff.len() as u32).to_le_bytes());
2278 exif_chunk.extend_from_slice(tiff);
2279 if tiff.len() % 2 == 1 {
2280 exif_chunk.push(0);
2281 }
2282 let mut vp8x = Vec::new();
2283 vp8x.extend_from_slice(b"VP8X");
2284 vp8x.extend_from_slice(&10u32.to_le_bytes());
2285 vp8x.extend_from_slice(&[0u8; 10]);
2286
2287 let mut body = Vec::new();
2288 body.extend_from_slice(b"WEBP");
2289 body.extend_from_slice(&vp8x);
2290 body.extend_from_slice(&exif_chunk);
2291
2292 let mut out = Vec::new();
2293 out.extend_from_slice(b"RIFF");
2294 out.extend_from_slice(&(body.len() as u32).to_le_bytes());
2295 out.extend_from_slice(&body);
2296 out
2297 }
2298
2299 #[test]
2300 fn parse_exif_webp_from_memory() {
2301 let tiff = webp_minimal_tiff_le();
2302 let buf = webp_with_exif(&tiff);
2303 let mut parser = MediaParser::new();
2304 let ms = MediaSource::from_memory(buf).unwrap();
2305 let iter = parser.parse_exif(ms).unwrap();
2306 let exif: crate::Exif = iter.into();
2307 assert_eq!(
2308 exif.get(crate::ExifTag::Make).and_then(|v| v.as_str()),
2309 Some("A")
2310 );
2311 }
2312
2313 #[test]
2314 fn parse_exif_webp_no_exif_returns_not_found() {
2315 let mut vp8x = Vec::new();
2317 vp8x.extend_from_slice(b"VP8X");
2318 vp8x.extend_from_slice(&10u32.to_le_bytes());
2319 vp8x.extend_from_slice(&[0u8; 10]);
2320 let mut body = Vec::new();
2321 body.extend_from_slice(b"WEBP");
2322 body.extend_from_slice(&vp8x);
2323 let mut buf = Vec::new();
2324 buf.extend_from_slice(b"RIFF");
2325 buf.extend_from_slice(&(body.len() as u32).to_le_bytes());
2326 buf.extend_from_slice(&body);
2327
2328 let mut parser = MediaParser::new();
2329 let ms = MediaSource::from_memory(buf).unwrap();
2330 let res = parser.parse_exif(ms);
2331 assert!(matches!(res, Err(crate::Error::ExifNotFound)));
2332 }
2333
2334 #[test]
2335 fn parse_exif_webp_streaming_small_file() {
2336 use std::io::Cursor;
2339 let tiff = webp_minimal_tiff_le();
2340 let buf = webp_with_exif(&tiff);
2341 let mut parser = MediaParser::new();
2342 let ms = MediaSource::seekable(Cursor::new(buf)).unwrap();
2343 let iter = parser.parse_exif(ms).unwrap();
2344 let exif: crate::Exif = iter.into();
2345 assert_eq!(
2346 exif.get(crate::ExifTag::Make).and_then(|v| v.as_str()),
2347 Some("A")
2348 );
2349 }
2350
2351 #[cfg(feature = "tokio")]
2352 #[tokio::test]
2353 async fn parse_exif_webp_async_from_memory() {
2354 use crate::AsyncMediaSource;
2355 let tiff = webp_minimal_tiff_le();
2356 let buf = webp_with_exif(&tiff);
2357 let mut parser = MediaParser::new();
2358 let ms = AsyncMediaSource::from_memory(buf).unwrap();
2359 let iter = parser.parse_exif_async(ms).await.unwrap();
2360 let exif: crate::Exif = iter.into();
2361 assert_eq!(
2362 exif.get(crate::ExifTag::Make).and_then(|v| v.as_str()),
2363 Some("A")
2364 );
2365 }
2366}