1use crate::error::{nom_error_to_parsing_error_with_state, ParsingError, ParsingErrorState};
2use crate::file::MimeImage;
3use crate::parser::{BufParser, ParsingState, ShareBuf};
4use crate::raf::RafInfo;
5use crate::skip::Skip;
6use crate::slice::SubsliceRange;
7use crate::{heif, jpeg, MediaParser, MediaSource};
8#[allow(deprecated)]
9use crate::{partial_vec::PartialVec, FileFormat};
10use exif_exif::check_exif_header2;
11pub use exif_exif::Exif;
12use exif_iter::input_into_iter;
13pub use exif_iter::{ExifIter, ParsedExifEntry};
14pub use gps::{GPSInfo, LatLng};
15pub use tags::ExifTag;
16
17use std::io::Read;
18use std::ops::Range;
19
20pub(crate) mod ifd;
21pub(crate) use exif_exif::{check_exif_header, TiffHeader};
22pub(crate) use travel::IfdHeaderTravel;
23
24mod exif_exif;
25mod exif_iter;
26mod gps;
27mod tags;
28mod travel;
29
30#[deprecated(since = "2.0.0")]
61#[allow(deprecated)]
62pub fn parse_exif<T: Read>(reader: T, _: Option<FileFormat>) -> crate::Result<Option<ExifIter>> {
63 let mut parser = MediaParser::new();
64 let iter: ExifIter = parser.parse(MediaSource::unseekable(reader)?)?;
65 let iter = iter.to_owned();
66 Ok(Some(iter))
67}
68
69#[tracing::instrument(skip(reader))]
70pub(crate) fn parse_exif_iter<R: Read, S: Skip<R>>(
71 parser: &mut MediaParser,
72 mime_img: MimeImage,
73 reader: &mut R,
74) -> Result<ExifIter, crate::Error> {
75 let out = parser.load_and_parse::<R, S, _, _>(reader, |buf, state| {
76 extract_exif_range(mime_img, buf, state)
77 })?;
78
79 range_to_iter(parser, out)
80}
81
82type ExifRangeResult = Result<Option<(Range<usize>, Option<TiffHeader>)>, ParsingErrorState>;
83
84fn extract_exif_range(img: MimeImage, buf: &[u8], state: Option<ParsingState>) -> ExifRangeResult {
85 let (exif_data, state) = extract_exif_with_mime(img, buf, state)?;
86 let header = state.and_then(|x| match x {
87 ParsingState::TiffHeader(h) => Some(h),
88 ParsingState::HeifExifSize(_) => None,
89 });
90 Ok(exif_data
91 .and_then(|x| buf.subslice_in_range(x))
92 .map(|x| (x, header)))
93}
94
95fn range_to_iter(
96 parser: &mut impl ShareBuf,
97 out: Option<(Range<usize>, Option<TiffHeader>)>,
98) -> Result<ExifIter, crate::Error> {
99 if let Some((range, header)) = out {
100 tracing::debug!(?range, ?header, "Got Exif data");
101 let input: PartialVec = parser.share_buf(range);
102 let iter = input_into_iter(input, header)?;
103
104 Ok(iter)
105 } else {
106 tracing::debug!("Exif not found");
107 Err("Exif not found".into())
108 }
109}
110
111#[cfg(feature = "async")]
112#[tracing::instrument(skip(reader))]
113pub(crate) async fn parse_exif_iter_async<
114 R: AsyncRead + Unpin + Send,
115 S: crate::skip::AsyncSkip<R>,
116>(
117 parser: &mut crate::AsyncMediaParser,
118 mime_img: MimeImage,
119 reader: &mut R,
120) -> Result<ExifIter, crate::Error> {
121 use crate::parser_async::AsyncBufParser;
122
123 let out = parser
124 .load_and_parse::<R, S, _, _>(reader, |buf, state| {
125 extract_exif_range(mime_img, buf, state)
126 })
127 .await?;
128
129 range_to_iter(parser, out)
130}
131
132pub(crate) fn extract_exif_with_mime(
133 img_type: crate::file::MimeImage,
134 buf: &[u8],
135 state: Option<ParsingState>,
136) -> Result<(Option<&[u8]>, Option<ParsingState>), ParsingErrorState> {
137 let (exif_data, state) = match img_type {
138 MimeImage::Jpeg => jpeg::extract_exif_data(buf)
139 .map(|res| (res.1, state.clone()))
140 .map_err(|e| nom_error_to_parsing_error_with_state(e, state))?,
141 MimeImage::Heic | crate::file::MimeImage::Heif => heif_extract_exif(state, buf)?,
142 MimeImage::Tiff => {
143 let (header, data_start) = match state {
144 Some(ParsingState::TiffHeader(ref h)) => (h.to_owned(), 0),
145 None => {
146 let (_, header) = TiffHeader::parse(buf)
147 .map_err(|e| nom_error_to_parsing_error_with_state(e, None))?;
148 if header.ifd0_offset as usize > buf.len() {
149 let clear_and_skip =
150 ParsingError::ClearAndSkip(header.ifd0_offset as usize);
151 let state = Some(ParsingState::TiffHeader(header));
152 return Err(ParsingErrorState::new(clear_and_skip, state));
153 }
154 let start = header.ifd0_offset as usize;
155 (header, start)
156 }
157 _ => unreachable!(),
158 };
159
160 let mut iter =
162 IfdHeaderTravel::new(&buf[data_start..], header.ifd0_offset, header.endian);
163 iter.travel_ifd(0)
164 .map_err(|e| ParsingErrorState::new(e, state.clone()))?;
165
166 (Some(buf), state)
167 }
168 MimeImage::Raf => RafInfo::parse(buf)
169 .map(|res| (res.1.exif_data, state.clone()))
170 .map_err(|e| nom_error_to_parsing_error_with_state(e, state))?,
171 };
172 Ok((exif_data, state))
173}
174
175fn heif_extract_exif(
176 state: Option<ParsingState>,
177 buf: &[u8],
178) -> Result<(Option<&[u8]>, Option<ParsingState>), ParsingErrorState> {
179 let (data, state) = match state {
180 Some(ParsingState::HeifExifSize(size)) => {
181 let (_, data) = nom::bytes::streaming::take(size)(buf)
182 .map_err(|e| nom_error_to_parsing_error_with_state(e, state.clone()))?;
183 (Some(data), state)
184 }
185 None => {
186 let (_, meta) = heif::parse_meta_box(buf)
187 .map_err(|e| nom_error_to_parsing_error_with_state(e, state))?;
188
189 if let Some(meta) = meta {
190 if let Some(range) = meta.exif_data_offset() {
191 if range.end > buf.len() {
192 let state = ParsingState::HeifExifSize(range.len());
193 let clear_and_skip = ParsingError::ClearAndSkip(range.start);
194 return Err(ParsingErrorState::new(clear_and_skip, Some(state)));
195 } else {
196 (Some(&buf[range]), None)
197 }
198 } else {
199 return Err(ParsingErrorState::new(
200 ParsingError::Failed("no exif offset in meta box".into()),
201 None,
202 ));
203 }
204 } else {
205 (None, None)
206 }
207 }
208 _ => unreachable!(),
209 };
210
211 let data = data.and_then(|x| check_exif_header2(x).map(|x| x.0).ok());
212
213 Ok((data, state))
214}
215
216#[cfg(feature = "async")]
217use tokio::io::AsyncRead;
218
219#[allow(deprecated)]
223#[cfg(feature = "async")]
224#[deprecated(since = "2.0.0")]
225pub async fn parse_exif_async<T: AsyncRead + Unpin + Send>(
226 reader: T,
227 _: Option<FileFormat>,
228) -> crate::Result<Option<ExifIter>> {
229 use crate::{AsyncMediaParser, AsyncMediaSource};
230
231 let mut parser = AsyncMediaParser::new();
232 let exif: ExifIter = parser
233 .parse(AsyncMediaSource::unseekable(reader).await?)
234 .await?;
235 Ok(Some(exif))
236}
237
238#[cfg(test)]
239#[allow(deprecated)]
240mod tests {
241 use std::{sync::mpsc, thread, time::Duration};
242
243 use crate::{
244 file::MimeImage,
245 testkit::{open_sample, read_sample},
246 values::URational,
247 };
248 use test_case::test_case;
249
250 use super::*;
251
252 #[test_case("exif.heic", "+43.29013+084.22713+1595.950CRSWGS_84/")]
253 #[test_case("exif.jpg", "+22.53113+114.02148/")]
254 fn gps(path: &str, gps_str: &str) {
255 let f = open_sample(path).unwrap();
256 let iter = parse_exif(f, None)
257 .expect("should be Ok")
258 .expect("should not be None");
259 let gps_info = iter
260 .parse_gps_info()
261 .expect("should be parsed Ok")
262 .expect("should not be None");
263
264 assert_eq!(gps_info.format_iso6709(), gps_str);
269 }
270
271 #[cfg(feature = "async")]
272 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
273 #[test_case("exif.heic", "+43.29013+084.22713+1595.950CRSWGS_84/")]
274 #[test_case("exif.jpg", "+22.53113+114.02148/")]
275 async fn gps_async(path: &str, gps_str: &str) {
276 use std::path::Path;
277 use tokio::fs::File;
278
279 let f = File::open(Path::new("testdata").join(path)).await.unwrap();
280 let iter = parse_exif_async(f, None)
281 .await
282 .expect("should be Ok")
283 .expect("should not be None");
284
285 let gps_str = gps_str.to_owned();
286 let _ = tokio::spawn(async move {
287 let exif: Exif = iter.into();
288 let gps_info = exif.get_gps_info().expect("ok").expect("some");
289 assert_eq!(gps_info.format_iso6709(), gps_str);
290 })
291 .await;
292 }
293
294 #[test_case(
295 "exif.jpg",
296 'N',
297 [(22, 1), (31, 1), (5208, 100)].into(),
298 'E',
299 [(114, 1), (1, 1), (1733, 100)].into(),
300 0u8,
301 (0, 1).into(),
302 None,
303 None
304 )]
305 #[allow(clippy::too_many_arguments)]
306 fn gps_info(
307 path: &str,
308 latitude_ref: char,
309 latitude: LatLng,
310 longitude_ref: char,
311 longitude: LatLng,
312 altitude_ref: u8,
313 altitude: URational,
314 speed_ref: Option<char>,
315 speed: Option<URational>,
316 ) {
317 let _ = tracing_subscriber::fmt().with_test_writer().try_init();
318
319 let buf = read_sample(path).unwrap();
320 let (data, _) = extract_exif_with_mime(MimeImage::Jpeg, &buf, None).unwrap();
321 let data = data.unwrap();
322
323 let subslice_range = buf.subslice_in_range(data).unwrap();
324 let iter = input_into_iter((buf, subslice_range), None).unwrap();
325 let exif: Exif = iter.into();
326
327 let gps = exif.get_gps_info().unwrap().unwrap();
328 assert_eq!(
329 gps,
330 GPSInfo {
331 latitude_ref,
332 latitude,
333 longitude_ref,
334 longitude,
335 altitude_ref,
336 altitude,
337 speed_ref,
338 speed,
339 }
340 )
341 }
342
343 #[test_case("exif.heic")]
344 fn tag_values(path: &str) {
345 let f = open_sample(path).unwrap();
346 let iter = parse_exif(f, None).unwrap().unwrap();
347 let tags = [ExifTag::Make, ExifTag::Model];
348 let res: Vec<String> = iter
349 .clone()
350 .filter(|e| e.tag().is_some_and(|t| tags.contains(&t)))
351 .filter(|e| e.has_value())
352 .map(|e| format!("{} => {}", e.tag().unwrap(), e.get_value().unwrap()))
353 .collect();
354 assert_eq!(res.join(", "), "Make => Apple, Model => iPhone 12 Pro");
355 }
356
357 #[test]
358 fn endless_loop() {
359 let (sender, receiver) = mpsc::channel();
360
361 thread::spawn(move || {
362 let name = "endless_loop.jpg";
363 let f = open_sample(name).unwrap();
364 let iter = parse_exif(f, None).unwrap().unwrap();
365 let _: Exif = iter.into();
366 sender.send(()).unwrap();
367 });
368
369 receiver
370 .recv_timeout(Duration::from_secs(1))
371 .expect("There is an infinite loop in the parsing process!");
372 }
373}