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::{cr3, heif, jpeg, MediaParser, MediaSource};
8#[allow(deprecated)]
9use crate::{partial_vec::PartialVec, FileFormat};
10pub use exif_exif::Exif;
11use exif_exif::TIFF_HEADER_LEN;
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, check_exif_header2, 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 if mime_img == MimeImage::Cr3 {
77 return parse_cr3_exif_iter::<R, S>(parser, reader);
78 }
79
80 let out = parser.load_and_parse::<R, S, _, _>(reader, |buf, state| {
81 extract_exif_range(mime_img, buf, state)
82 })?;
83
84 range_to_iter(parser, out)
85}
86
87#[tracing::instrument(skip(reader))]
90fn parse_cr3_exif_iter<R: Read, S: Skip<R>>(
91 parser: &mut MediaParser,
92 reader: &mut R,
93) -> Result<ExifIter, crate::Error> {
94 use crate::parser::Buf;
95
96 let cmt_ranges = parser
98 .load_and_parse::<R, S, _, _>(reader, |buf, _state| cr3::extract_all_cmt_ranges(buf))?;
99
100 let Some(cmt_ranges) = cmt_ranges else {
101 return Err("CR3: No CMT data found".into());
102 };
103
104 if cmt_ranges.ranges.is_empty() {
105 return Err("CR3: No CMT ranges available".into());
106 }
107
108 tracing::debug!(
109 cmt_count = cmt_ranges.ranges.len(),
110 "Found CMT ranges in CR3 file"
111 );
112
113 let position_offset = parser.position();
115
116 let (first_block_id, first_range) = &cmt_ranges.ranges[0];
118 tracing::debug!(
119 block_id = first_block_id,
120 range = ?first_range,
121 position_offset,
122 "Creating primary ExifIter from first CMT block"
123 );
124
125 let input: PartialVec = parser.share_buf(first_range.clone());
128 let mut iter = input_into_iter(input, None)?;
129
130 for (block_id, range) in cmt_ranges.ranges.iter().skip(1) {
136 if *block_id == "CMT3" {
138 tracing::debug!(block_id, "Skipping CMT3 (MakerNotes) - proprietary format");
139 continue;
140 }
141
142 let adjusted_range = (range.start + position_offset)..(range.end + position_offset);
143 tracing::debug!(
144 block_id,
145 original_range = ?range,
146 adjusted_range = ?adjusted_range,
147 "Adding additional CMT block"
148 );
149 iter.add_tiff_block(block_id.to_string(), adjusted_range, None);
150 }
151
152 Ok(iter)
153}
154
155type ExifRangeResult = Result<Option<(Range<usize>, Option<TiffHeader>)>, ParsingErrorState>;
156
157fn extract_exif_range(img: MimeImage, buf: &[u8], state: Option<ParsingState>) -> ExifRangeResult {
158 let (exif_data, state) = extract_exif_with_mime(img, buf, state)?;
159 let header = state.and_then(|x| match x {
160 ParsingState::TiffHeader(h) => Some(h),
161 ParsingState::HeifExifSize(_) => None,
162 ParsingState::Cr3ExifSize(_) => None,
163 });
164 Ok(exif_data
165 .and_then(|x| buf.subslice_in_range(x))
166 .map(|x| (x, header)))
167}
168
169fn range_to_iter(
170 parser: &mut impl ShareBuf,
171 out: Option<(Range<usize>, Option<TiffHeader>)>,
172) -> Result<ExifIter, crate::Error> {
173 if let Some((range, header)) = out {
174 tracing::debug!(?range, ?header, "Got Exif data");
175 let input: PartialVec = parser.share_buf(range);
176 let iter = input_into_iter(input, header)?;
177
178 Ok(iter)
179 } else {
180 tracing::debug!("Exif not found");
181 Err("Exif not found".into())
182 }
183}
184
185#[cfg(feature = "async")]
186#[tracing::instrument(skip(reader))]
187pub(crate) async fn parse_exif_iter_async<
188 R: AsyncRead + Unpin + Send,
189 S: crate::skip::AsyncSkip<R>,
190>(
191 parser: &mut crate::AsyncMediaParser,
192 mime_img: MimeImage,
193 reader: &mut R,
194) -> Result<ExifIter, crate::Error> {
195 use crate::parser_async::AsyncBufParser;
196
197 let out = parser
198 .load_and_parse::<R, S, _, _>(reader, |buf, state| {
199 extract_exif_range(mime_img, buf, state)
200 })
201 .await?;
202
203 range_to_iter(parser, out)
204}
205
206#[tracing::instrument(skip(buf))]
207pub(crate) fn extract_exif_with_mime(
208 img_type: crate::file::MimeImage,
209 buf: &[u8],
210 state: Option<ParsingState>,
211) -> Result<(Option<&[u8]>, Option<ParsingState>), ParsingErrorState> {
212 let (exif_data, state) = match img_type {
213 MimeImage::Jpeg => jpeg::extract_exif_data(buf)
214 .map(|res| (res.1, state.clone()))
215 .map_err(|e| nom_error_to_parsing_error_with_state(e, state))?,
216 MimeImage::Heic | crate::file::MimeImage::Heif => heif_extract_exif(state, buf)?,
217 MimeImage::Tiff => {
218 let header = match state {
219 Some(ParsingState::TiffHeader(ref h)) => h.to_owned(),
220 None => {
221 let (_, header) = TiffHeader::parse(buf)
222 .map_err(|e| nom_error_to_parsing_error_with_state(e, None))?;
223 if header.ifd0_offset as usize > buf.len() {
224 let clear_and_skip =
225 ParsingError::Need(header.ifd0_offset as usize - TIFF_HEADER_LEN + 2);
226 let state = Some(ParsingState::TiffHeader(header));
227 return Err(ParsingErrorState::new(clear_and_skip, state));
228 }
229 header
230 }
231 _ => unreachable!(),
232 };
233
234 tracing::debug!("full fill TIFF data");
236 let mut iter = IfdHeaderTravel::new(
237 buf,
238 header.ifd0_offset as usize,
239 tags::ExifTagCode::Code(0x2a),
240 header.endian,
241 );
242 iter.travel_ifd(0)
243 .map_err(|e| ParsingErrorState::new(e, state.clone()))?;
244 tracing::debug!("full fill TIFF data done");
245
246 (Some(buf), state)
247 }
248 MimeImage::Raf => RafInfo::parse(buf)
249 .map(|res| (res.1.exif_data, state.clone()))
250 .map_err(|e| nom_error_to_parsing_error_with_state(e, state))?,
251 MimeImage::Cr3 => cr3_extract_exif(state, buf)?,
252 };
253 Ok((exif_data, state))
254}
255
256fn heif_extract_exif(
257 state: Option<ParsingState>,
258 buf: &[u8],
259) -> Result<(Option<&[u8]>, Option<ParsingState>), ParsingErrorState> {
260 heif::extract_exif_data(state, buf)
261}
262
263fn cr3_extract_exif(
264 state: Option<ParsingState>,
265 buf: &[u8],
266) -> Result<(Option<&[u8]>, Option<ParsingState>), ParsingErrorState> {
267 cr3::extract_exif_data(state, buf)
268}
269
270#[cfg(feature = "async")]
271use tokio::io::AsyncRead;
272
273#[allow(deprecated)]
277#[cfg(feature = "async")]
278#[deprecated(since = "2.0.0")]
279pub async fn parse_exif_async<T: AsyncRead + Unpin + Send>(
280 reader: T,
281 _: Option<FileFormat>,
282) -> crate::Result<Option<ExifIter>> {
283 use crate::{AsyncMediaParser, AsyncMediaSource};
284
285 let mut parser = AsyncMediaParser::new();
286 let exif: ExifIter = parser
287 .parse(AsyncMediaSource::unseekable(reader).await?)
288 .await?;
289 Ok(Some(exif))
290}
291
292#[cfg(test)]
293#[allow(deprecated)]
294mod tests {
295 use std::{sync::mpsc, thread, time::Duration};
296
297 use crate::{
298 file::MimeImage,
299 testkit::{open_sample, read_sample},
300 values::URational,
301 };
302 use test_case::test_case;
303
304 use super::*;
305
306 #[test_case("exif.heic", "+43.29013+084.22713+1595.950CRSWGS_84/")]
307 #[test_case("exif.jpg", "+22.53113+114.02148/")]
308 #[test_case("invalid-gps", "-")]
309 fn gps(path: &str, gps_str: &str) {
310 let f = open_sample(path).unwrap();
311 let iter = parse_exif(f, None)
312 .expect("should be Ok")
313 .expect("should not be None");
314
315 if gps_str == "-" {
316 assert!(iter.parse_gps_info().expect("should be ok").is_none());
317 } else {
318 let gps_info = iter
319 .parse_gps_info()
320 .expect("should be parsed Ok")
321 .expect("should not be None");
322
323 assert_eq!(gps_info.format_iso6709(), gps_str);
328 }
329 }
330
331 #[cfg(feature = "async")]
332 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
333 #[test_case("exif.heic", "+43.29013+084.22713+1595.950CRSWGS_84/")]
334 #[test_case("exif.jpg", "+22.53113+114.02148/")]
335 async fn gps_async(path: &str, gps_str: &str) {
336 use std::path::Path;
337 use tokio::fs::File;
338
339 let f = File::open(Path::new("testdata").join(path)).await.unwrap();
340 let iter = parse_exif_async(f, None)
341 .await
342 .expect("should be Ok")
343 .expect("should not be None");
344
345 let gps_str = gps_str.to_owned();
346 let _ = tokio::spawn(async move {
347 let exif: Exif = iter.into();
348 let gps_info = exif.get_gps_info().expect("ok").expect("some");
349 assert_eq!(gps_info.format_iso6709(), gps_str);
350 })
351 .await;
352 }
353
354 #[test_case(
355 "exif.jpg",
356 'N',
357 [(22, 1), (31, 1), (5208, 100)].into(),
358 'E',
359 [(114, 1), (1, 1), (1733, 100)].into(),
360 0u8,
361 (0, 1).into(),
362 None,
363 None
364 )]
365 #[allow(clippy::too_many_arguments)]
366 fn gps_info(
367 path: &str,
368 latitude_ref: char,
369 latitude: LatLng,
370 longitude_ref: char,
371 longitude: LatLng,
372 altitude_ref: u8,
373 altitude: URational,
374 speed_ref: Option<char>,
375 speed: Option<URational>,
376 ) {
377 let _ = tracing_subscriber::fmt().with_test_writer().try_init();
378
379 let buf = read_sample(path).unwrap();
380 let (data, _) = extract_exif_with_mime(MimeImage::Jpeg, &buf, None).unwrap();
381 let data = data.unwrap();
382
383 let subslice_in_range = buf.subslice_in_range(data).unwrap();
384 let iter = input_into_iter((buf, subslice_in_range), None).unwrap();
385 let exif: Exif = iter.into();
386
387 let gps = exif.get_gps_info().unwrap().unwrap();
388 assert_eq!(
389 gps,
390 GPSInfo {
391 latitude_ref,
392 latitude,
393 longitude_ref,
394 longitude,
395 altitude_ref,
396 altitude,
397 speed_ref,
398 speed,
399 }
400 )
401 }
402
403 #[test_case("exif.heic")]
404 fn tag_values(path: &str) {
405 let f = open_sample(path).unwrap();
406 let iter = parse_exif(f, None).unwrap().unwrap();
407 let tags = [ExifTag::Make, ExifTag::Model];
408 let res: Vec<String> = iter
409 .clone()
410 .filter(|e| e.tag().is_some_and(|t| tags.contains(&t)))
411 .filter(|e| e.has_value())
412 .map(|e| format!("{} => {}", e.tag().unwrap(), e.get_value().unwrap()))
413 .collect();
414 assert_eq!(res.join(", "), "Make => Apple, Model => iPhone 12 Pro");
415 }
416
417 #[test]
418 fn endless_loop() {
419 let (sender, receiver) = mpsc::channel();
420
421 thread::spawn(move || {
422 let name = "endless_loop.jpg";
423 let f = open_sample(name).unwrap();
424 let iter = parse_exif(f, None).unwrap().unwrap();
425 let _: Exif = iter.into();
426 sender.send(()).unwrap();
427 });
428
429 receiver
430 .recv_timeout(Duration::from_secs(1))
431 .expect("There is an infinite loop in the parsing process!");
432 }
433}