From 763ae22f4fa14b926cff219bc5080c6dd0a45845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hubert=20Figui=C3=A8re?= Date: Mon, 24 Apr 2023 23:52:47 -0400 Subject: [PATCH] Read the XMP packet - and expose it like exif --- examples/decode.rs | 1 + src/decoder.rs | 13 +++++++++++++ src/parser.rs | 45 +++++++++++++++++++++++++++++++-------------- tests/lib.rs | 14 ++++++++++++++ 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/examples/decode.rs b/examples/decode.rs index 13934fe0..eee4e27d 100644 --- a/examples/decode.rs +++ b/examples/decode.rs @@ -23,6 +23,7 @@ fn main() { eprintln!("{:?}", info); eprintln!("Exif: {}", decoder.exif_data().is_some()); + eprintln!("XMP: {}", decoder.xmp_data().is_some()); eprintln!("ICC: {}", decoder.icc_profile().is_some()); let output_file = File::create(output_path).unwrap(); diff --git a/src/decoder.rs b/src/decoder.rs index 795ad1e4..fcf1812f 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -118,6 +118,8 @@ pub struct Decoder { icc_markers: Vec, exif_data: Option>, + xmp_data: Option>, + psir_data: Option>, // Used for progressive JPEGs. coefficients: Vec>, @@ -144,6 +146,8 @@ impl Decoder { is_mjpeg: false, icc_markers: Vec::new(), exif_data: None, + xmp_data: None, + psir_data: None, coefficients: Vec::new(), coefficients_finished: [0; MAX_COMPONENTS], decoding_buffer_size_limit: usize::MAX, @@ -197,6 +201,13 @@ impl Decoder { self.exif_data.as_deref() } + /// Returns the raw XMP packet if there is any. + /// + /// The returned value will be `None` until a call to `decode` has returned `Ok`. + pub fn xmp_data(&self) -> Option<&[u8]> { + self.xmp_data.as_deref() + } + /// Returns the embeded icc profile if the image contains one. pub fn icc_profile(&self) -> Option> { let mut marker_present: [Option<&IccChunk>; 256] = [None; 256]; @@ -542,6 +553,8 @@ impl Decoder { AppData::Avi1 => self.is_mjpeg = true, AppData::Icc(icc) => self.icc_markers.push(icc), AppData::Exif(data) => self.exif_data = Some(data), + AppData::Xmp(data) => self.xmp_data = Some(data), + AppData::Psir(data) => self.psir_data = Some(data), } } } diff --git a/src/parser.rs b/src/parser.rs index 72ba00dc..03383e52 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -95,6 +95,8 @@ pub enum AppData { Avi1, Icc(IccChunk), Exif(Vec), + Xmp(Vec), + Psir(Vec), } // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe @@ -621,21 +623,20 @@ pub fn parse_app(reader: &mut R, marker: Marker) -> Result { - if length >= 6 { - let mut buffer = [0u8; 6]; - reader.read_exact(&mut buffer)?; - bytes_read = buffer.len(); - - // https://web.archive.org/web/20190624045241if_/http://www.cipa.jp:80/std/documents/e/DC-008-Translation-2019-E.pdf - // 4.5.4 Basic Structure of JPEG Compressed Data - if buffer == *b"Exif\x00\x00" { - let mut data = vec![0; length - bytes_read]; - reader.read_exact(&mut data)?; - bytes_read += data.len(); - result = Some(AppData::Exif(data)); - } + let mut buffer = vec![0u8; length]; + reader.read_exact(&mut buffer)?; + bytes_read = buffer.len(); + + // https://web.archive.org/web/20190624045241if_/http://www.cipa.jp:80/std/documents/e/DC-008-Translation-2019-E.pdf + // 4.5.4 Basic Structure of JPEG Compressed Data + if length >= 6 && buffer[0..6] == *b"Exif\x00\x00" { + result = Some(AppData::Exif(buffer[6..].to_vec())); + } + // XMP packet + // https://github.com/adobe/XMP-Toolkit-SDK/blob/main/docs/XMPSpecificationPart3.pdf + else if length >= 29 && buffer[0..29] == *b"http://ns.adobe.com/xap/1.0/\0" { + result = Some(AppData::Xmp(buffer[29..].to_vec())); } } APP(2) => { @@ -658,6 +659,22 @@ pub fn parse_app(reader: &mut R, marker: Marker) -> Result { + if length >= 14 { + let mut buffer = [0u8; 14]; + reader.read_exact(&mut buffer)?; + bytes_read = buffer.len(); + + // PSIR (Photoshop) + // https://github.com/adobe/XMP-Toolkit-SDK/blob/main/docs/XMPSpecificationPart3.pdf + if buffer[0..14] == *b"Photoshop 3.0\0" { + let mut data = vec![0; length - bytes_read]; + reader.read_exact(&mut data)?; + bytes_read += data.len(); + result = Some(AppData::Psir(data)); + } + } + } APP(14) => { if length >= 12 { let mut buffer = [0u8; 12]; diff --git a/tests/lib.rs b/tests/lib.rs index 3ee186be..037da227 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -154,3 +154,17 @@ fn read_exif_data() { // exif data start as a TIFF header assert_eq!(&exif_data[0..8], b"\x49\x49\x2A\x00\x08\x00\x00\x00"); } + +#[test] +fn read_xmp_data() { + let path = Path::new("tests") + .join("reftest") + .join("images") + .join("ycck.jpg"); + + let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + decoder.decode().unwrap(); + + let xmp_data = decoder.xmp_data().unwrap(); + assert_eq!(&xmp_data[0..9], b"