Skip to content

Commit 763ae22

Browse files
committed
Read the XMP packet
- and expose it like exif
1 parent 509ec66 commit 763ae22

File tree

4 files changed

+59
-14
lines changed

4 files changed

+59
-14
lines changed

examples/decode.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ fn main() {
2323

2424
eprintln!("{:?}", info);
2525
eprintln!("Exif: {}", decoder.exif_data().is_some());
26+
eprintln!("XMP: {}", decoder.xmp_data().is_some());
2627
eprintln!("ICC: {}", decoder.icc_profile().is_some());
2728

2829
let output_file = File::create(output_path).unwrap();

src/decoder.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ pub struct Decoder<R> {
118118
icc_markers: Vec<IccChunk>,
119119

120120
exif_data: Option<Vec<u8>>,
121+
xmp_data: Option<Vec<u8>>,
122+
psir_data: Option<Vec<u8>>,
121123

122124
// Used for progressive JPEGs.
123125
coefficients: Vec<Vec<i16>>,
@@ -144,6 +146,8 @@ impl<R: Read> Decoder<R> {
144146
is_mjpeg: false,
145147
icc_markers: Vec::new(),
146148
exif_data: None,
149+
xmp_data: None,
150+
psir_data: None,
147151
coefficients: Vec::new(),
148152
coefficients_finished: [0; MAX_COMPONENTS],
149153
decoding_buffer_size_limit: usize::MAX,
@@ -197,6 +201,13 @@ impl<R: Read> Decoder<R> {
197201
self.exif_data.as_deref()
198202
}
199203

204+
/// Returns the raw XMP packet if there is any.
205+
///
206+
/// The returned value will be `None` until a call to `decode` has returned `Ok`.
207+
pub fn xmp_data(&self) -> Option<&[u8]> {
208+
self.xmp_data.as_deref()
209+
}
210+
200211
/// Returns the embeded icc profile if the image contains one.
201212
pub fn icc_profile(&self) -> Option<Vec<u8>> {
202213
let mut marker_present: [Option<&IccChunk>; 256] = [None; 256];
@@ -542,6 +553,8 @@ impl<R: Read> Decoder<R> {
542553
AppData::Avi1 => self.is_mjpeg = true,
543554
AppData::Icc(icc) => self.icc_markers.push(icc),
544555
AppData::Exif(data) => self.exif_data = Some(data),
556+
AppData::Xmp(data) => self.xmp_data = Some(data),
557+
AppData::Psir(data) => self.psir_data = Some(data),
545558
}
546559
}
547560
}

src/parser.rs

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ pub enum AppData {
9595
Avi1,
9696
Icc(IccChunk),
9797
Exif(Vec<u8>),
98+
Xmp(Vec<u8>),
99+
Psir(Vec<u8>),
98100
}
99101

100102
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
@@ -621,21 +623,20 @@ pub fn parse_app<R: Read>(reader: &mut R, marker: Marker) -> Result<Option<AppDa
621623
}
622624
}
623625
}
624-
// Exif Data
625626
APP(1) => {
626-
if length >= 6 {
627-
let mut buffer = [0u8; 6];
628-
reader.read_exact(&mut buffer)?;
629-
bytes_read = buffer.len();
630-
631-
// https://web.archive.org/web/20190624045241if_/http://www.cipa.jp:80/std/documents/e/DC-008-Translation-2019-E.pdf
632-
// 4.5.4 Basic Structure of JPEG Compressed Data
633-
if buffer == *b"Exif\x00\x00" {
634-
let mut data = vec![0; length - bytes_read];
635-
reader.read_exact(&mut data)?;
636-
bytes_read += data.len();
637-
result = Some(AppData::Exif(data));
638-
}
627+
let mut buffer = vec![0u8; length];
628+
reader.read_exact(&mut buffer)?;
629+
bytes_read = buffer.len();
630+
631+
// https://web.archive.org/web/20190624045241if_/http://www.cipa.jp:80/std/documents/e/DC-008-Translation-2019-E.pdf
632+
// 4.5.4 Basic Structure of JPEG Compressed Data
633+
if length >= 6 && buffer[0..6] == *b"Exif\x00\x00" {
634+
result = Some(AppData::Exif(buffer[6..].to_vec()));
635+
}
636+
// XMP packet
637+
// https://github.com/adobe/XMP-Toolkit-SDK/blob/main/docs/XMPSpecificationPart3.pdf
638+
else if length >= 29 && buffer[0..29] == *b"http://ns.adobe.com/xap/1.0/\0" {
639+
result = Some(AppData::Xmp(buffer[29..].to_vec()));
639640
}
640641
}
641642
APP(2) => {
@@ -658,6 +659,22 @@ pub fn parse_app<R: Read>(reader: &mut R, marker: Marker) -> Result<Option<AppDa
658659
}
659660
}
660661
}
662+
APP(13) => {
663+
if length >= 14 {
664+
let mut buffer = [0u8; 14];
665+
reader.read_exact(&mut buffer)?;
666+
bytes_read = buffer.len();
667+
668+
// PSIR (Photoshop)
669+
// https://github.com/adobe/XMP-Toolkit-SDK/blob/main/docs/XMPSpecificationPart3.pdf
670+
if buffer[0..14] == *b"Photoshop 3.0\0" {
671+
let mut data = vec![0; length - bytes_read];
672+
reader.read_exact(&mut data)?;
673+
bytes_read += data.len();
674+
result = Some(AppData::Psir(data));
675+
}
676+
}
677+
}
661678
APP(14) => {
662679
if length >= 12 {
663680
let mut buffer = [0u8; 12];

tests/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,17 @@ fn read_exif_data() {
154154
// exif data start as a TIFF header
155155
assert_eq!(&exif_data[0..8], b"\x49\x49\x2A\x00\x08\x00\x00\x00");
156156
}
157+
158+
#[test]
159+
fn read_xmp_data() {
160+
let path = Path::new("tests")
161+
.join("reftest")
162+
.join("images")
163+
.join("ycck.jpg");
164+
165+
let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap());
166+
decoder.decode().unwrap();
167+
168+
let xmp_data = decoder.xmp_data().unwrap();
169+
assert_eq!(&xmp_data[0..9], b"<?xpacket");
170+
}

0 commit comments

Comments
 (0)