1use little_exif::endian::Endian;
12use little_exif::exif_tag::ExifTag;
13use little_exif::exif_tag_format::ExifTagFormat;
14use little_exif::filetype::FileExtension;
15use little_exif::ifd::ExifTagGroup;
16use little_exif::metadata::Metadata;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Entry {
21 pub tag: u16,
22 pub typ: u16,
23 pub count: u32,
24 pub value: Vec<u8>,
25}
26
27#[derive(Debug, Default, Clone, PartialEq, Eq)]
29pub struct ExifData {
30 pub ifd0: Vec<Entry>,
31 pub exif: Vec<Entry>,
32 pub gps: Vec<Entry>,
33}
34
35const TAG_EXIF_IFD: u16 = 0x8769;
36const TAG_GPS_IFD: u16 = 0x8825;
37const TAG_INTEROP_IFD: u16 = 0xA005;
38const TAG_MAKER_NOTE: u16 = 0x927C;
39const TAG_PIXEL_X_DIMENSION: u16 = 0xA002;
40const TAG_PIXEL_Y_DIMENSION: u16 = 0xA003;
41const TAG_ORIENTATION: u16 = 0x0112;
42
43const IFD0_TAGS: &[u16] = &[
46 0x010E, 0x010F, 0x0110, TAG_ORIENTATION,
50 0x0131, 0x0132, 0x013B, 0x8298, ];
55
56const MAX_VALUE_LEN: usize = 4096;
59
60fn type_sizes(typ: u16) -> Option<(usize, usize)> {
63 Some(match typ {
64 1 | 2 | 6 | 7 => (1, 1), 3 | 8 => (2, 2), 4 | 9 | 11 | 13 => (4, 4), 5 | 10 => (8, 4), 12 => (8, 8), _ => return None,
70 })
71}
72
73struct Tiff<'a> {
75 data: &'a [u8],
76 little_endian: bool,
77}
78
79impl<'a> Tiff<'a> {
80 fn parse(data: &'a [u8]) -> Option<(Self, u32)> {
84 let little_endian = match data.get(0..2)? {
85 b"II" => true,
86 b"MM" => false,
87 _ => return None,
88 };
89 let tiff = Tiff {
90 data,
91 little_endian,
92 };
93 let magic = tiff.u16(2)?;
94 if !matches!(magic, 0x2A | 0x4F52 | 0x5352 | 0x55) {
95 return None;
96 }
97 let first_ifd = tiff.u32(4)?;
98 Some((tiff, first_ifd))
99 }
100
101 fn u16(&self, at: usize) -> Option<u16> {
102 let b = self.data.get(at..at + 2)?;
103 Some(if self.little_endian {
104 u16::from_le_bytes([b[0], b[1]])
105 } else {
106 u16::from_be_bytes([b[0], b[1]])
107 })
108 }
109
110 fn u32(&self, at: usize) -> Option<u32> {
111 let b = self.data.get(at..at + 4)?;
112 Some(if self.little_endian {
113 u32::from_le_bytes([b[0], b[1], b[2], b[3]])
114 } else {
115 u32::from_be_bytes([b[0], b[1], b[2], b[3]])
116 })
117 }
118
119 fn ifd(&self, offset: u32) -> Option<Vec<Entry>> {
122 let offset = offset as usize;
123 let count = self.u16(offset)? as usize;
124 let mut entries = Vec::with_capacity(count);
125 for i in 0..count {
126 let at = offset + 2 + i * 12;
127 let tag = self.u16(at)?;
128 let typ = self.u16(at + 2)?;
129 let n = self.u32(at + 4)?;
130 let Some((elem, unit)) = type_sizes(typ) else {
131 continue;
132 };
133 let Some(len) = (n as usize).checked_mul(elem) else {
134 continue;
135 };
136 let value_at = if len <= 4 {
137 at + 8
138 } else {
139 self.u32(at + 8)? as usize
140 };
141 let Some(bytes) = value_at
142 .checked_add(len)
143 .and_then(|end| self.data.get(value_at..end))
144 else {
145 continue;
146 };
147 let mut value = bytes.to_vec();
148 if !self.little_endian && unit > 1 {
149 for chunk in value.chunks_mut(unit) {
150 chunk.reverse();
151 }
152 }
153 entries.push(Entry {
154 tag,
155 typ,
156 count: n,
157 value,
158 });
159 }
160 Some(entries)
161 }
162
163 fn pointer(ifd: &[Entry], tag: u16) -> Option<u32> {
165 let entry = ifd.iter().find(|e| e.tag == tag)?;
166 let b = entry.value.get(0..4)?;
167 Some(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
168 }
169}
170
171fn copyable(entry: &Entry) -> bool {
172 entry.value.len() <= MAX_VALUE_LEN
173}
174
175pub fn read_tiff_raw(data: &[u8]) -> Option<ExifData> {
178 let (tiff, ifd0_offset) = Tiff::parse(data)?;
179 let ifd0 = tiff.ifd(ifd0_offset)?;
180
181 let exif = Tiff::pointer(&ifd0, TAG_EXIF_IFD)
182 .and_then(|o| tiff.ifd(o))
183 .unwrap_or_default();
184 let gps = Tiff::pointer(&ifd0, TAG_GPS_IFD)
185 .and_then(|o| tiff.ifd(o))
186 .unwrap_or_default();
187
188 Some(ExifData {
189 ifd0: ifd0
190 .into_iter()
191 .filter(|e| IFD0_TAGS.contains(&e.tag) && copyable(e))
192 .collect(),
193 exif: exif.into_iter().filter(keep_exif_entry).collect(),
194 gps: gps.into_iter().filter(copyable).collect(),
195 })
196}
197
198fn keep_exif_entry(entry: &Entry) -> bool {
199 !matches!(
200 entry.tag,
201 TAG_MAKER_NOTE
202 | TAG_INTEROP_IFD
203 | TAG_EXIF_IFD
204 | TAG_GPS_IFD
205 | TAG_PIXEL_X_DIMENSION
207 | TAG_PIXEL_Y_DIMENSION
208 ) && entry.typ != 13
209 && copyable(entry)
210}
211
212pub fn read_cr3(data: &[u8]) -> Option<ExifData> {
217 if data.get(4..12)? != b"ftypcrx " {
218 return None;
219 }
220 let search = &data[..data.len().min(4 * 1024 * 1024)];
221 let ifd_of = |name: &[u8; 4]| -> Option<Vec<Entry>> {
222 let at = search.windows(4).position(|w| w == name)?;
223 let size = u32::from_be_bytes(search.get(at.checked_sub(4)?..at)?.try_into().ok()?);
224 let payload = search.get(at + 4..(at - 4).checked_add(size as usize)?)?;
225 let (tiff, offset) = Tiff::parse(payload)?;
226 tiff.ifd(offset)
227 };
228
229 let ifd0 = ifd_of(b"CMT1")?;
230 Some(ExifData {
231 ifd0: ifd0
232 .into_iter()
233 .filter(|e| IFD0_TAGS.contains(&e.tag) && copyable(e))
234 .collect(),
235 exif: ifd_of(b"CMT2")
236 .unwrap_or_default()
237 .into_iter()
238 .filter(keep_exif_entry)
239 .collect(),
240 gps: ifd_of(b"CMT4")
241 .unwrap_or_default()
242 .into_iter()
243 .filter(copyable)
244 .collect(),
245 })
246}
247
248pub fn read_raw(data: &[u8]) -> Option<ExifData> {
250 read_tiff_raw(data).or_else(|| read_cr3(data))
251}
252
253impl ExifData {
254 pub fn is_empty(&self) -> bool {
255 self.ifd0.is_empty() && self.exif.is_empty() && self.gps.is_empty()
256 }
257
258 pub fn write_into_jpeg(&self, jpeg: &mut Vec<u8>) -> std::io::Result<()> {
262 let mut metadata = Metadata::new();
263 let groups = [
264 (&self.ifd0, ExifTagGroup::GENERIC),
265 (&self.exif, ExifTagGroup::EXIF),
266 (&self.gps, ExifTagGroup::GPS),
267 ];
268 for (entries, group) in groups {
269 for e in entries {
270 let Some(format) = ExifTagFormat::from_u16(e.typ) else {
271 continue;
272 };
273 if let Ok(tag) =
274 ExifTag::from_u16_with_data(e.tag, &format, &e.value, &Endian::Little, &group)
275 {
276 metadata.set_tag(tag);
277 }
278 }
279 }
280 metadata.write_to_vec(jpeg, FileExtension::JPEG)
281 }
282}
283
284#[cfg(test)]
285pub(crate) mod tests {
286 use super::*;
287
288 impl ExifData {
289 pub fn orientation(&self) -> Option<u16> {
291 let e = self.ifd0.iter().find(|e| e.tag == TAG_ORIENTATION)?;
292 (e.typ == 3 && e.value.len() >= 2).then(|| u16::from_le_bytes([e.value[0], e.value[1]]))
293 }
294 }
295
296 pub fn fake_tiff_raw_be() -> Vec<u8> {
299 let make = b"Canon\0";
301 let date = b"2024:06:01 12:34:56\0";
302 let ifd0_entries = 6; let ifd0_at = 8;
304 let exif_at = ifd0_at + 2 + ifd0_entries * 12 + 4;
305 let exif_entries = 3; let gps_at = exif_at + 2 + exif_entries * 12 + 4;
307 let gps_entries = 1; let data_at = gps_at + 2 + gps_entries * 12 + 4;
309
310 let mut t = b"MM\x00\x2A".to_vec();
311 t.extend((ifd0_at as u32).to_be_bytes());
312
313 let mut data: Vec<u8> = Vec::new();
314 let mut put = |bytes: &[u8]| -> u32 {
315 let at = data_at + data.len();
316 data.extend(bytes);
317 at as u32
318 };
319 let make_at = put(make);
320 let date_at = put(date);
321 let exposure_at = put(&[0, 0, 0, 1, 0, 0, 0, 125]);
322 let dto_at = put(date);
323 let maker_note_at = put(&[0xAB; 64]);
324
325 let entry = |t: &mut Vec<u8>, tag: u16, typ: u16, count: u32, value: [u8; 4]| {
326 t.extend(tag.to_be_bytes());
327 t.extend(typ.to_be_bytes());
328 t.extend(count.to_be_bytes());
329 t.extend(value);
330 };
331
332 t.extend((ifd0_entries as u16).to_be_bytes());
333 entry(&mut t, 0x010F, 2, make.len() as u32, make_at.to_be_bytes());
334 entry(&mut t, 0x0111, 4, 1, 12345u32.to_be_bytes()); entry(&mut t, TAG_ORIENTATION, 3, 1, [0, 6, 0, 0]);
336 entry(&mut t, 0x0132, 2, date.len() as u32, date_at.to_be_bytes());
337 entry(&mut t, TAG_EXIF_IFD, 4, 1, (exif_at as u32).to_be_bytes());
338 entry(&mut t, TAG_GPS_IFD, 4, 1, (gps_at as u32).to_be_bytes());
339 t.extend(0u32.to_be_bytes());
340
341 t.extend((exif_entries as u16).to_be_bytes());
342 entry(&mut t, 0x829A, 5, 1, exposure_at.to_be_bytes());
343 entry(&mut t, 0x9003, 2, date.len() as u32, dto_at.to_be_bytes());
344 entry(&mut t, TAG_MAKER_NOTE, 7, 64, maker_note_at.to_be_bytes());
345 t.extend(0u32.to_be_bytes());
346
347 t.extend((gps_entries as u16).to_be_bytes());
348 entry(&mut t, 0x0001, 2, 2, *b"N\0\0\0");
349 t.extend(0u32.to_be_bytes());
350
351 assert_eq!(t.len(), data_at);
352 t.extend(data);
353 t
354 }
355
356 #[test]
357 fn reads_tiff_raw_and_filters_tags() {
358 let exif = read_raw(&fake_tiff_raw_be()).unwrap();
359
360 let ifd0_tags: Vec<u16> = exif.ifd0.iter().map(|e| e.tag).collect();
361 assert_eq!(ifd0_tags, vec![0x010F, TAG_ORIENTATION, 0x0132]);
362 assert_eq!(exif.orientation(), Some(6));
363 assert_eq!(exif.ifd0[0].value, b"Canon\0");
364
365 let exif_tags: Vec<u16> = exif.exif.iter().map(|e| e.tag).collect();
366 assert_eq!(exif_tags, vec![0x829A, 0x9003]);
367 assert_eq!(exif.exif[0].value, vec![1, 0, 0, 0, 125, 0, 0, 0]);
369
370 assert_eq!(exif.gps.len(), 1);
371 }
372
373 pub fn exif_tiff(jpeg: &[u8]) -> Option<&[u8]> {
375 let mut p = 2;
376 while jpeg.get(p) == Some(&0xFF) && jpeg.get(p + 1) != Some(&0xDA) {
377 let len = u16::from_be_bytes([*jpeg.get(p + 2)?, *jpeg.get(p + 3)?]) as usize;
378 let segment = jpeg.get(p + 4..p + 2 + len)?;
379 if jpeg[p + 1] == 0xE1 && segment.starts_with(b"Exif\0\0") {
380 return Some(&segment[6..]);
381 }
382 p += 2 + len;
383 }
384 None
385 }
386
387 #[test]
388 fn written_exif_reads_back() {
389 let exif = read_raw(&fake_tiff_raw_be()).unwrap();
390 let mut jpeg = crate::raw::jpeg::tests::fake_jpeg(0xC0, 1920, 1280, None);
391 exif.write_into_jpeg(&mut jpeg).unwrap();
392
393 let found = crate::raw::jpeg::find_jpegs(&jpeg);
395 assert_eq!(found.len(), 1);
396 assert!(found[0].has_exif);
397
398 let reread = read_tiff_raw(exif_tiff(&jpeg).unwrap()).unwrap();
400 assert_eq!(reread, exif);
401 }
402
403 #[test]
404 fn reads_cr3_metadata_boxes() {
405 let mut cmt1 = b"II\x2A\x00\x08\x00\x00\x00".to_vec();
407 cmt1.extend(2u16.to_le_bytes());
408 cmt1.extend(0x0110u16.to_le_bytes());
409 cmt1.extend(2u16.to_le_bytes());
410 cmt1.extend(4u32.to_le_bytes());
411 cmt1.extend(*b"R5\0\0");
412 cmt1.extend(TAG_ORIENTATION.to_le_bytes());
413 cmt1.extend(3u16.to_le_bytes());
414 cmt1.extend(1u32.to_le_bytes());
415 cmt1.extend([8, 0, 0, 0]);
416 cmt1.extend(0u32.to_le_bytes());
417
418 let mut data = vec![0, 0, 0, 0x18];
419 data.extend(b"ftypcrx ");
420 data.extend([0u8; 12]);
421 data.extend(((cmt1.len() + 8) as u32).to_be_bytes());
422 data.extend(b"CMT1");
423 data.extend(&cmt1);
424
425 let exif = read_raw(&data).unwrap();
426 assert_eq!(exif.ifd0.len(), 2);
427 assert_eq!(exif.orientation(), Some(8));
428 assert!(exif.exif.is_empty());
429 }
430}