From a6461ef7d545a3ed2771dcf9ab2cb33536a5d989 Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Fri, 28 May 2021 13:48:05 -0400 Subject: [PATCH] Improve --- .cargo/config.toml | 2 + .gitignore | 1 + Cargo.lock | 141 +++++++++++++++++++ Cargo.toml | 6 + src/reader.rs | 132 +++++++++++------- src/tag.rs | 2 + src/tiff.rs | 336 +++++++++++++++++++++++++++++++++------------ src/value.rs | 17 ++- tests/rwrcmp.rs | 83 +++++------ 9 files changed, 539 insertions(+), 181 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..62459f5 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["--cfg", "unsound_local_offset"] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c2a96b3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,141 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "const_fn" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" + +[[package]] +name = "exif" +version = "0.5.4" +dependencies = [ + "mutate_once", + "serde", + "time", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "libc" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" + +[[package]] +name = "mutate_once" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "standback" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b4edc6d0d2bcf00cb27fd366af60458ed67deb68ee810ac4c2807275d4d26bf" +dependencies = [ + "version_check", +] + +[[package]] +name = "syn" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "time" +version = "0.3.0-alpha-0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bb952abad871c7aed4bf9335aa9152bdcc17ec0637de5480a08e00d845224f" +dependencies = [ + "const_fn", + "itoa", + "libc", + "standback", + "winapi", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 2941e00..fa40c5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,9 @@ license = "BSD-2-Clause" [dependencies] mutate_once = "0.1.1" + +time = {version = "0.3.0-alpha-0", features = ["local-offset"], optional = true} +serde = { version = "1", features = ["derive"], optional = true } + +[dev-dependencies] +time = {version = "0.3.0-alpha-0", features = ["local-offset", "formatting"]} diff --git a/src/reader.rs b/src/reader.rs index dd76bf1..11b6090 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -59,8 +59,7 @@ use crate::webp; /// "72 pixels per inch"); /// # Ok(()) } /// ``` -pub struct Reader { -} +pub struct Reader {} impl Reader { /// Constructs a new `Reader`. @@ -70,11 +69,14 @@ impl Reader { /// Parses the Exif attributes from raw Exif data. /// If an error occurred, `exif::Error` is returned. - pub fn read_raw(&self, data: Vec) -> Result { + pub fn read_raw>(&self, data: Buf) -> Result, Error> { let buf = data; - let (entries, le) = tiff::parse_exif(&buf)?; - let entry_map = entries.iter().enumerate() - .map(|(i, e)| (e.ifd_num_tag(), i)).collect(); + let (entries, le) = tiff::parse_exif(buf.as_ref())?; + let entry_map = entries + .iter() + .enumerate() + .map(|(i, e)| (e.ifd_num_tag(), i)) + .collect(); Ok(Exif { buf: buf, @@ -96,8 +98,10 @@ impl Reader { /// /// This method is provided for the convenience even though /// parsing containers is basically out of the scope of this library. - pub fn read_from_container(&self, reader: &mut R) -> Result - where R: io::BufRead + io::Seek { + pub fn read_from_container<'a, R>(&self, reader: &mut R) -> Result>, Error> + where + R: io::BufRead + io::Seek, + { let mut buf = Vec::new(); reader.by_ref().take(4096).read_to_end(&mut buf)?; if tiff::is_tiff(&buf) { @@ -139,9 +143,8 @@ impl Reader { /// } /// # Some(()) } /// ``` -pub struct Exif { - // TIFF data. - buf: Vec, +pub struct Exif> { + buf: Buf, // Exif fields. Vec is used to keep the ability to enumerate all fields // even if there are duplicates. entries: Vec, @@ -151,18 +154,32 @@ pub struct Exif { little_endian: bool, } -impl Exif { +impl> Exif { /// Returns the slice that contains the TIFF data. #[inline] pub fn buf(&self) -> &[u8] { - &self.buf[..] + self.buf.as_ref() } /// Returns an iterator of Exif fields. #[inline] - pub fn fields(&self) -> impl ExactSizeIterator { - self.entries.iter() - .map(move |e| e.ref_field(&self.buf, self.little_endian)) + pub fn fields<'a>(&'a self) -> impl ExactSizeIterator { + let buf = self.buf.as_ref(); + let little_endian = self.little_endian; + self.entries + .iter() + .map(move |e| e.ref_field(buf, little_endian)) + } + + /// Returns an iterator of Exif fields. + #[inline] + pub fn into_fields(mut self) -> Vec { + let buf = self.buf.as_ref(); + let little_endian = self.little_endian; + self.entries + .drain(..) + .map(|e| e.into_field(buf, little_endian)) + .collect() } /// Returns true if the Exif data (TIFF structure) is in the @@ -176,12 +193,13 @@ impl Exif { /// and the IFD number. #[inline] pub fn get_field(&self, tag: Tag, ifd_num: In) -> Option<&Field> { - self.entry_map.get(&(ifd_num, tag)) - .map(|&i| self.entries[i].ref_field(&self.buf, self.little_endian)) + self.entry_map + .get(&(ifd_num, tag)) + .map(|&i| self.entries[i].ref_field(self.buf.as_ref(), self.little_endian)) } } -impl<'a> ProvideUnit<'a> for &'a Exif { +impl<'a, Buf: AsRef<[u8]>> ProvideUnit<'a> for &'a Exif { fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field> { self.get_field(tag, ifd_num) } @@ -189,69 +207,80 @@ impl<'a> ProvideUnit<'a> for &'a Exif { #[cfg(test)] mod tests { - use std::fs::File; - use std::io::BufReader; + use super::*; use crate::tag::Context; use crate::value::Value; - use super::*; + use std::fs::File; + use std::io::BufReader; #[test] fn get_field() { let file = File::open("tests/yaminabe.tif").unwrap(); - let exif = Reader::new().read_from_container( - &mut BufReader::new(&file)).unwrap(); + let exif = Reader::new() + .read_from_container(&mut BufReader::new(&file)) + .unwrap(); match exif.get_field(Tag::ImageDescription, In(0)).unwrap().value { Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test image"]), - ref v => panic!("wrong variant {:?}", v) + ref v => panic!("wrong variant {:?}", v), } match exif.get_field(Tag::ImageDescription, In(1)).unwrap().value { Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test thumbnail"]), - ref v => panic!("wrong variant {:?}", v) + ref v => panic!("wrong variant {:?}", v), } match exif.get_field(Tag::ImageDescription, In(2)).unwrap().value { Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test 2nd IFD"]), - ref v => panic!("wrong variant {:?}", v) + ref v => panic!("wrong variant {:?}", v), } } #[test] fn display_value_with_unit() { let file = File::open("tests/yaminabe.tif").unwrap(); - let exif = Reader::new().read_from_container( - &mut BufReader::new(&file)).unwrap(); + let exif = Reader::new() + .read_from_container(&mut BufReader::new(&file)) + .unwrap(); // No unit. let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap(); - assert_eq!(exifver.display_value().with_unit(&exif).to_string(), - "2.31"); + assert_eq!(exifver.display_value().with_unit(&exif).to_string(), "2.31"); // Fixed string. let width = exif.get_field(Tag::ImageWidth, In::PRIMARY).unwrap(); - assert_eq!(width.display_value().with_unit(&exif).to_string(), - "17 pixels"); + assert_eq!( + width.display_value().with_unit(&exif).to_string(), + "17 pixels" + ); // Unit tag (with a non-default value). let gpsalt = exif.get_field(Tag::GPSAltitude, In::PRIMARY).unwrap(); - assert_eq!(gpsalt.display_value().with_unit(&exif).to_string(), - "0.5 meters below sea level"); + assert_eq!( + gpsalt.display_value().with_unit(&exif).to_string(), + "0.5 meters below sea level" + ); // Unit tag is missing but the default is specified. let xres = exif.get_field(Tag::XResolution, In::PRIMARY).unwrap(); - assert_eq!(xres.display_value().with_unit(&exif).to_string(), - "72 pixels per inch"); + assert_eq!( + xres.display_value().with_unit(&exif).to_string(), + "72 pixels per inch" + ); // Unit tag is missing and the default is not specified. let gpslat = exif.get_field(Tag::GPSLatitude, In::PRIMARY).unwrap(); - assert_eq!(gpslat.display_value().with_unit(&exif).to_string(), - "10 deg 0 min 0 sec [GPSLatitudeRef missing]"); + assert_eq!( + gpslat.display_value().with_unit(&exif).to_string(), + "10 deg 0 min 0 sec [GPSLatitudeRef missing]" + ); } #[test] fn yaminabe() { let file = File::open("tests/yaminabe.tif").unwrap(); - let be = Reader::new().read_from_container( - &mut BufReader::new(&file)).unwrap(); + let be = Reader::new() + .read_from_container(&mut BufReader::new(&file)) + .unwrap(); let file = File::open("tests/yaminale.tif").unwrap(); - let le = Reader::new().read_from_container( - &mut BufReader::new(&file)).unwrap(); + let le = Reader::new() + .read_from_container(&mut BufReader::new(&file)) + .unwrap(); assert!(!be.little_endian()); assert!(le.little_endian()); - for exif in &[be, le] { + for exif in [be, le] { assert_eq!(exif.fields().len(), 26); let f = exif.get_field(Tag::ImageWidth, In(0)).unwrap(); assert_eq!(f.display_value().to_string(), "17"); @@ -273,8 +302,9 @@ mod tests { #[test] fn heif() { let file = std::fs::File::open("tests/exif.heic").unwrap(); - let exif = Reader::new().read_from_container( - &mut std::io::BufReader::new(&file)).unwrap(); + let exif = Reader::new() + .read_from_container(&mut std::io::BufReader::new(&file)) + .unwrap(); assert_eq!(exif.fields().len(), 2); let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap(); assert_eq!(exifver.display_value().to_string(), "2.31"); @@ -283,8 +313,9 @@ mod tests { #[test] fn png() { let file = std::fs::File::open("tests/exif.png").unwrap(); - let exif = Reader::new().read_from_container( - &mut std::io::BufReader::new(&file)).unwrap(); + let exif = Reader::new() + .read_from_container(&mut std::io::BufReader::new(&file)) + .unwrap(); assert_eq!(exif.fields().len(), 6); let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap(); assert_eq!(exifver.display_value().to_string(), "2.32"); @@ -293,8 +324,9 @@ mod tests { #[test] fn webp() { let file = std::fs::File::open("tests/exif.webp").unwrap(); - let exif = Reader::new().read_from_container( - &mut std::io::BufReader::new(&file)).unwrap(); + let exif = Reader::new() + .read_from_container(&mut std::io::BufReader::new(&file)) + .unwrap(); assert_eq!(exif.fields().len(), 6); let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap(); assert_eq!(exifver.display_value().to_string(), "2.32"); diff --git a/src/tag.rs b/src/tag.rs index 68f43f9..09abebd 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -56,6 +56,7 @@ use crate::util::atou16; // emulate structural equivalency. // #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Tag(pub Context, pub u16); impl Tag { @@ -114,6 +115,7 @@ impl fmt::Display for Tag { /// An enum that indicates how a tag number is interpreted. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[non_exhaustive] pub enum Context { /// TIFF attributes defined in the TIFF Rev. 6.0 specification. diff --git a/src/tiff.rs b/src/tiff.rs index 9b15bc6..3011648 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -24,16 +24,16 @@ // SUCH DAMAGE. // -use std::fmt; use mutate_once::MutOnce; +use std::fmt; -use crate::endian::{Endian, BigEndian, LittleEndian}; +use crate::endian::{BigEndian, Endian, LittleEndian}; use crate::error::Error; use crate::tag::{Context, Tag, UnitPiece}; -use crate::value; -use crate::value::Value; -use crate::value::get_type_info; use crate::util::{atou16, ctou32}; +use crate::value; +use crate::value::get_type_info; +use crate::value::Value; // TIFF header magic numbers [EXIF23 4.5.2]. const TIFF_BE: u16 = 0x4d4d; @@ -68,7 +68,7 @@ impl IfdEntry { self.field.get_ref() } - fn into_field(self, data: &[u8], le: bool) -> Field { + pub fn into_field(self, data: &[u8], le: bool) -> Field { self.parse(data, le); self.field.into_inner() } @@ -85,21 +85,25 @@ impl IfdEntry { } // Converts a partially parsed value into a real one. - fn parse_value(value: &mut Value, data: &[u8]) where E: Endian { + fn parse_value(value: &mut Value, data: &[u8]) + where + E: Endian, + { match *value { Value::Unknown(typ, cnt, ofs) => { let (unitlen, parser) = get_type_info::(typ); if unitlen != 0 { *value = parser(data, ofs as usize, cnt as usize); } - }, + } _ => panic!("value is already parsed"), } } } /// A TIFF/Exif field. -#[derive(Debug, Clone)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Field { /// The tag of this field. pub tag: Tag, @@ -121,7 +125,8 @@ pub struct Field { /// assert_eq!(In::PRIMARY.index(), 0); /// assert_eq!(In::THUMBNAIL.index(), 1); /// ``` -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct In(pub u16); impl In { @@ -152,8 +157,10 @@ impl fmt::Display for In { /// If an error occurred, `exif::Error` is returned. pub fn parse_exif_compat03(data: &[u8]) -> Result<(Vec, bool), Error> { parse_exif(data).map(|(entries, le)| { - let fields = entries.into_iter() - .map(|e| e.into_field(data, le)).collect(); + let fields = entries + .into_iter() + .map(|e| e.into_field(data, le)) + .collect(); (fields, le) }) } @@ -170,8 +177,10 @@ pub fn parse_exif(data: &[u8]) -> Result<(Vec, bool), Error> { } } -fn parse_exif_sub(data: &[u8]) - -> Result, Error> where E: Endian { +fn parse_exif_sub(data: &[u8]) -> Result, Error> +where + E: Endian, +{ // Parse the rest of the header (42 and the IFD offset). if E::loadu16(data, 2) != TIFF_FORTY_TWO { return Err(Error::InvalidFormat("Invalid forty two")); @@ -186,17 +195,23 @@ fn parse_exif_sub(data: &[u8]) if ifd_num >= 8 { return Err(Error::InvalidFormat("Limit the IFD count to 8")); } - ifd_offset = parse_ifd::( - &mut entries, data, ifd_offset, Context::Tiff, ifd_num)?; + ifd_offset = parse_ifd::(&mut entries, data, ifd_offset, Context::Tiff, ifd_num)?; ifd_num_ck = ifd_num.checked_add(1); } Ok(entries) } // Parse IFD [EXIF23 4.6.2]. -fn parse_ifd(entries: &mut Vec, data: &[u8], - offset: usize, ctx: Context, ifd_num: u16) - -> Result where E: Endian { +fn parse_ifd( + entries: &mut Vec, + data: &[u8], + offset: usize, + ctx: Context, + ifd_num: u16, +) -> Result +where + E: Endian, +{ // Count (the number of the entries). if data.len() < offset || data.len() - offset < 2 { return Err(Error::InvalidFormat("Truncated IFD count")); @@ -213,8 +228,9 @@ fn parse_ifd(entries: &mut Vec, data: &[u8], let cnt = E::loadu32(data, offset + 2 + i * 12 + 4); let valofs_at = offset + 2 + i * 12 + 8; let (unitlen, _parser) = get_type_info::(typ); - let vallen = unitlen.checked_mul(cnt as usize).ok_or( - Error::InvalidFormat("Invalid entry count"))?; + let vallen = unitlen + .checked_mul(cnt as usize) + .ok_or(Error::InvalidFormat("Invalid entry count"))?; let mut val = if vallen <= 4 { Value::Unknown(typ, cnt, valofs_at as u32) } else { @@ -229,14 +245,23 @@ fn parse_ifd(entries: &mut Vec, data: &[u8], // recursively defined. let tag = Tag(ctx, tag); match tag { - Tag::ExifIFDPointer => parse_child_ifd::( - entries, data, &mut val, Context::Exif, ifd_num)?, - Tag::GPSInfoIFDPointer => parse_child_ifd::( - entries, data, &mut val, Context::Gps, ifd_num)?, - Tag::InteropIFDPointer => parse_child_ifd::( - entries, data, &mut val, Context::Interop, ifd_num)?, - _ => entries.push(IfdEntry { field: Field { - tag: tag, ifd_num: In(ifd_num), value: val }.into()}), + Tag::ExifIFDPointer => { + parse_child_ifd::(entries, data, &mut val, Context::Exif, ifd_num)? + } + Tag::GPSInfoIFDPointer => { + parse_child_ifd::(entries, data, &mut val, Context::Gps, ifd_num)? + } + Tag::InteropIFDPointer => { + parse_child_ifd::(entries, data, &mut val, Context::Interop, ifd_num)? + } + _ => entries.push(IfdEntry { + field: Field { + tag: tag, + ifd_num: In(ifd_num), + value: val, + } + .into(), + }), } } @@ -248,17 +273,25 @@ fn parse_ifd(entries: &mut Vec, data: &[u8], Ok(next_ifd_offset) } -fn parse_child_ifd(entries: &mut Vec, data: &[u8], - pointer: &mut Value, ctx: Context, ifd_num: u16) - -> Result<(), Error> where E: Endian { +fn parse_child_ifd( + entries: &mut Vec, + data: &[u8], + pointer: &mut Value, + ctx: Context, + ifd_num: u16, +) -> Result<(), Error> +where + E: Endian, +{ // The pointer is not yet parsed, so do it here. IfdEntry::parse_value::(pointer, data); // A pointer field has type == LONG and count == 1, so the // value (IFD offset) must be embedded in the "value offset" // element of the field. - let ofs = pointer.get_uint(0).ok_or( - Error::InvalidFormat("Invalid pointer"))? as usize; + let ofs = pointer + .get_uint(0) + .ok_or(Error::InvalidFormat("Invalid pointer"))? as usize; match parse_ifd::(entries, data, ofs, ctx, ifd_num)? { 0 => Ok(()), _ => Err(Error::InvalidFormat("Unexpected next IFD")), @@ -280,7 +313,8 @@ pub fn is_tiff(buf: &[u8]) -> bool { /// assert_eq!(dt.to_string(), "2016-05-04 03:02:01"); /// # Ok(()) } /// ``` -#[derive(Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DateTime { pub year: u16, pub month: u8, @@ -305,8 +339,12 @@ impl DateTime { return Err(Error::BlankValue("DateTime is blank")); } else if data.len() < 19 { return Err(Error::InvalidFormat("DateTime too short")); - } else if !(data[4] == b':' && data[7] == b':' && data[10] == b' ' && - data[13] == b':' && data[16] == b':') { + } else if !(data[4] == b':' + && data[7] == b':' + && data[10] == b' ' + && data[13] == b':' + && data[16] == b':') + { return Err(Error::InvalidFormat("Invalid DateTime delimiter")); } Ok(DateTime { @@ -365,13 +403,103 @@ impl DateTime { }); Ok(()) } + + /// Parses an SubsecTime-like field. + pub fn with_subsec(mut self, data: &[u8]) -> Result { + let mut subsec = 0; + let mut ndigits = 0; + for &c in data { + if c == b' ' { + break; + } + subsec = subsec * 10 + + match ctou32(c) { + Ok(it) => it, + Err(e) => return Err((e, self)), + }; + ndigits += 1; + if ndigits >= 9 { + break; + } + } + if ndigits == 0 { + self.nanosecond = None; + } else { + for _ in ndigits..9 { + subsec *= 10; + } + self.nanosecond = Some(subsec); + } + Ok(self) + } + + /// Parses an OffsetTime-like field. + pub fn with_offset(mut self, data: &[u8]) -> Result { + if data == b" : " || data == b" " { + return Err((Error::BlankValue("OffsetTime is blank"), self)); + } else if data.len() < 6 { + return Err((Error::InvalidFormat("OffsetTime too short"), self)); + } else if data[3] != b':' { + return Err((Error::InvalidFormat("Invalid OffsetTime delimiter"), self)); + } + let hour = match atou16(&data[1..3]) { + Ok(it) => it, + Err(e) => return Err((e, self)), + }; + let min = match atou16(&data[4..6]) { + Ok(it) => it, + Err(e) => return Err((e, self)), + }; + let offset = (hour * 60 + min) as i16; + self.offset = Some(match data[0] { + b'+' => offset, + b'-' => -offset, + _ => return Err((Error::InvalidFormat("Invalid OffsetTime sign"), self)), + }); + Ok(self) + } } impl fmt::Display for DateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", - self.year, self.month, self.day, - self.hour, self.minute, self.second) + write!( + f, + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", + self.year, self.month, self.day, self.hour, self.minute, self.second + ) + } +} + +#[cfg(feature = "time")] +impl core::convert::TryFrom for time::OffsetDateTime { + // TODO fill in a type + type Error = (); + + fn try_from(value: DateTime) -> core::result::Result { + time::OffsetDateTime::try_from(&value) + } +} + +#[cfg(feature = "time")] +impl core::convert::TryFrom<&DateTime> for time::OffsetDateTime { + // TODO fill in a type + type Error = (); + + fn try_from(value: &DateTime) -> core::result::Result { + let naive = time::PrimitiveDateTime::new( + time::Date::from_calendar_date(value.year as i32, value.month, value.day).map_err(|_| ())?, + time::Time::from_hms_nano(value.hour, value.minute, value.second, value.nanosecond.unwrap_or(0)).map_err(|_| ())?, + ); + let offset = value + .offset + .ok_or(()) + .and_then(|o| { + let h = o % 60; + let m = o - (h * 60); + time::UtcOffset::from_hms(h as i8, m as i8, 0).map_err(|_| ()) + }) + .unwrap_or(time::UtcOffset::current_local_offset().unwrap_or(time::UtcOffset::UTC)); + Ok(naive.assume_offset(offset)) } } @@ -442,8 +570,10 @@ pub struct DisplayValue<'a> { impl<'a> DisplayValue<'a> { #[inline] - pub fn with_unit(&self, unit_provider: T) - -> DisplayValueUnit<'a, T> where T: ProvideUnit<'a> { + pub fn with_unit(&self, unit_provider: T) -> DisplayValueUnit<'a, T> + where + T: ProvideUnit<'a>, + { DisplayValueUnit { ifd_num: self.ifd_num, value_display: self.value_display, @@ -461,14 +591,20 @@ impl<'a> fmt::Display for DisplayValue<'a> { } /// Helper struct for printing a value with its unit. -pub struct DisplayValueUnit<'a, T> where T: ProvideUnit<'a> { +pub struct DisplayValueUnit<'a, T> +where + T: ProvideUnit<'a>, +{ ifd_num: In, value_display: value::Display<'a>, unit: Option<&'static [UnitPiece]>, unit_provider: T, } -impl<'a, T> fmt::Display for DisplayValueUnit<'a, T> where T: ProvideUnit<'a> { +impl<'a, T> fmt::Display for DisplayValueUnit<'a, T> +where + T: ProvideUnit<'a>, +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(unit) = self.unit { assert!(!unit.is_empty()); @@ -476,15 +612,15 @@ impl<'a, T> fmt::Display for DisplayValueUnit<'a, T> where T: ProvideUnit<'a> { match *piece { UnitPiece::Value => self.value_display.fmt(f), UnitPiece::Str(s) => f.write_str(s), - UnitPiece::Tag(tag) => - if let Some(x) = self.unit_provider.get_field( - tag, self.ifd_num) { + UnitPiece::Tag(tag) => { + if let Some(x) = self.unit_provider.get_field(tag, self.ifd_num) { x.value.display_as(tag).fmt(f) } else if let Some(x) = tag.default_value() { x.display_as(tag).fmt(f) } else { write!(f, "[{} missing]", tag) - }, + } + } }? } Ok(()) @@ -533,9 +669,9 @@ mod tests { #[test] fn truncated() { - let mut data = - b"MM\0\x2a\0\0\0\x08\ - \0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\0".to_vec(); + let mut data = b"MM\0\x2a\0\0\0\x08\ + \0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\0" + .to_vec(); parse_exif(&data).unwrap(); while let Some(_) = data.pop() { parse_exif(&data).unwrap_err(); @@ -548,8 +684,10 @@ mod tests { fn inf_loop_by_next() { let data = b"MM\0\x2a\0\0\0\x08\ \0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\x08"; - assert_err_pat!(parse_exif(data), - Error::InvalidFormat("Limit the IFD count to 8")); + assert_err_pat!( + parse_exif(data), + Error::InvalidFormat("Limit the IFD count to 8") + ); } #[test] @@ -559,8 +697,10 @@ mod tests { \x00\x00\x00\x00\ \x00\x01\x90\x00\x00\x07\x00\x00\x00\x040231\ \x00\x00\x00\x08"; - assert_err_pat!(parse_exif(data), - Error::InvalidFormat("Unexpected next IFD")); + assert_err_pat!( + parse_exif(data), + Error::InvalidFormat("Unexpected next IFD") + ); } #[test] @@ -569,8 +709,10 @@ mod tests { \0\x01\x01\0\xff\xff\0\0\0\x01\0\x14\0\0\0\0\0\0"; let (v, le) = parse_exif(data).unwrap(); assert_eq!(v.len(), 1); - assert_pat!(v[0].ref_field(data, le).value, - Value::Unknown(0xffff, 1, 0x12)); + assert_pat!( + v[0].ref_field(data, le).value, + Value::Unknown(0xffff, 1, 0x12) + ); } #[test] @@ -610,6 +752,23 @@ mod tests { assert_err_pat!(dt.parse_offset(b" "), Error::BlankValue(_)); } + #[cfg(feature = "time")] + #[test] + fn date_time_chrono() { + let mut dt = DateTime::from_ascii(b"2016:05:04 03:02:01").unwrap(); + use core::convert::TryFrom; + let fmt = time::format_description::parse( + "[year]-[month]-[day]T[hour]:[minute]:[second][offset_hour \ + sign:mandatory]:[offset_minute]", + ).unwrap(); + let dt_str = time::OffsetDateTime::try_from(dt) + .unwrap() + .format(&fmt).unwrap(); + assert_eq!(dt_str, "2016-05-04T03:02:01-04:00"); + assert_ne!(dt_str, "2016-05-04T03:02:01+04:00"); + assert_ne!(dt_str, "2016-05-04T03:02:01Z"); + } + #[test] fn display_value_with_unit() { let cm = Field { @@ -628,24 +787,24 @@ mod tests { ifd_num: In::PRIMARY, value: Value::Undefined(b"0231".to_vec(), 0), }; - assert_eq!(exifver.display_value().to_string(), - "2.31"); - assert_eq!(exifver.display_value().with_unit(()).to_string(), - "2.31"); - assert_eq!(exifver.display_value().with_unit(&cm).to_string(), - "2.31"); + assert_eq!(exifver.display_value().to_string(), "2.31"); + assert_eq!(exifver.display_value().with_unit(()).to_string(), "2.31"); + assert_eq!(exifver.display_value().with_unit(&cm).to_string(), "2.31"); // Fixed string. let width = Field { tag: Tag::ImageWidth, ifd_num: In::PRIMARY, value: Value::Short(vec![257]), }; - assert_eq!(width.display_value().to_string(), - "257"); - assert_eq!(width.display_value().with_unit(()).to_string(), - "257 pixels"); - assert_eq!(width.display_value().with_unit(&cm).to_string(), - "257 pixels"); + assert_eq!(width.display_value().to_string(), "257"); + assert_eq!( + width.display_value().with_unit(()).to_string(), + "257 pixels" + ); + assert_eq!( + width.display_value().with_unit(&cm).to_string(), + "257 pixels" + ); // Unit tag (with a non-default value). // Unit tag is missing but the default is specified. let xres = Field { @@ -653,27 +812,34 @@ mod tests { ifd_num: In::PRIMARY, value: Value::Rational(vec![(300, 1).into()]), }; - assert_eq!(xres.display_value().to_string(), - "300"); - assert_eq!(xres.display_value().with_unit(()).to_string(), - "300 pixels per inch"); - assert_eq!(xres.display_value().with_unit(&cm).to_string(), - "300 pixels per cm"); - assert_eq!(xres.display_value().with_unit(&cm_tn).to_string(), - "300 pixels per inch"); + assert_eq!(xres.display_value().to_string(), "300"); + assert_eq!( + xres.display_value().with_unit(()).to_string(), + "300 pixels per inch" + ); + assert_eq!( + xres.display_value().with_unit(&cm).to_string(), + "300 pixels per cm" + ); + assert_eq!( + xres.display_value().with_unit(&cm_tn).to_string(), + "300 pixels per inch" + ); // Unit tag is missing and the default is not specified. let gpslat = Field { tag: Tag::GPSLatitude, ifd_num: In::PRIMARY, - value: Value::Rational(vec![ - (10, 1).into(), (0, 1).into(), (1, 10).into()]), + value: Value::Rational(vec![(10, 1).into(), (0, 1).into(), (1, 10).into()]), }; - assert_eq!(gpslat.display_value().to_string(), - "10 deg 0 min 0.1 sec"); - assert_eq!(gpslat.display_value().with_unit(()).to_string(), - "10 deg 0 min 0.1 sec [GPSLatitudeRef missing]"); - assert_eq!(gpslat.display_value().with_unit(&cm).to_string(), - "10 deg 0 min 0.1 sec [GPSLatitudeRef missing]"); + assert_eq!(gpslat.display_value().to_string(), "10 deg 0 min 0.1 sec"); + assert_eq!( + gpslat.display_value().with_unit(()).to_string(), + "10 deg 0 min 0.1 sec [GPSLatitudeRef missing]" + ); + assert_eq!( + gpslat.display_value().with_unit(&cm).to_string(), + "10 deg 0 min 0.1 sec [GPSLatitudeRef missing]" + ); } #[test] diff --git a/src/value.rs b/src/value.rs index 86d844f..adf7df5 100644 --- a/src/value.rs +++ b/src/value.rs @@ -30,7 +30,8 @@ use std::fmt::Write as _; use crate::endian::Endian; /// A type and values of a TIFF/Exif field. -#[derive(Clone)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Value { /// Vector of 8-bit unsigned integers. Byte(Vec), @@ -96,7 +97,7 @@ impl Value { /// Returns the value as a slice if the type is BYTE. #[inline] - pub(crate) fn byte(&self) -> Option<&[u8]> { + pub fn byte(&self) -> Option<&[u8]> { match *self { Value::Byte(ref v) => Some(v), _ => None, @@ -105,7 +106,7 @@ impl Value { /// Returns the value as `AsciiValues` if the type is ASCII. #[inline] - pub(crate) fn ascii(&self) -> Option { + pub fn ascii(&self) -> Option { match *self { Value::Ascii(ref v) => Some(AsciiValues(v)), _ => None, @@ -114,7 +115,7 @@ impl Value { /// Returns the value as a slice if the type is RATIONAL. #[inline] - pub(crate) fn rational(&self) -> Option<&[Rational]> { + pub fn rational(&self) -> Option<&[Rational]> { match *self { Value::Rational(ref v) => Some(v), _ => None, @@ -123,7 +124,7 @@ impl Value { /// Returns the value as a slice if the type is UNDEFINED. #[inline] - pub(crate) fn undefined(&self) -> Option<&[u8]> { + pub fn undefined(&self) -> Option<&[u8]> { match *self { Value::Undefined(ref v, _) => Some(v), _ => None, @@ -296,7 +297,8 @@ impl From<&DefaultValue> for Option { } /// An unsigned rational number, which is a pair of 32-bit unsigned integers. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Rational { pub num: u32, pub denom: u32 } impl Rational { @@ -346,7 +348,8 @@ impl From for f32 { } /// A signed rational number, which is a pair of 32-bit signed integers. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SRational { pub num: i32, pub denom: i32 } impl SRational { diff --git a/tests/rwrcmp.rs b/tests/rwrcmp.rs index 7a5254e..f1b438c 100644 --- a/tests/rwrcmp.rs +++ b/tests/rwrcmp.rs @@ -35,10 +35,10 @@ use std::fs::File; use std::io::{BufReader, Cursor}; use std::path::Path; +use exif::experimental::Writer; #[cfg(not(test))] use exif::Error; -use exif::{Exif, In, Reader, Value, Tag}; -use exif::experimental::Writer; +use exif::{Exif, In, Reader, Tag, Value}; #[test] fn exif_heic() { @@ -71,7 +71,10 @@ fn main() { } } -fn rwr_compare

(path: P) where P: AsRef { +fn rwr_compare

(path: P) +where + P: AsRef, +{ let path = path.as_ref(); // Read. @@ -85,7 +88,7 @@ fn rwr_compare

(path: P) where P: AsRef { Err(e) => { println!("{}: {}: Skipped", path.display(), e); return; - }, + } }; let strips = get_strips(&exif1, In::PRIMARY); let tn_strips = get_strips(&exif1, In::THUMBNAIL); @@ -114,11 +117,11 @@ fn rwr_compare

(path: P) where P: AsRef { writer.write(&mut out, exif1.little_endian()).unwrap(); #[cfg(not(test))] match writer.write(&mut out, exif1.little_endian()) { - Ok(_) => {}, + Ok(_) => {} Err(Error::NotSupported(_)) => { println!("{}: Contains unknown type", path.display()); return; - }, + } e => e.unwrap(), } let out = out.into_inner(); @@ -138,9 +141,8 @@ fn rwr_compare

(path: P) where P: AsRef { assert_eq!(f1.tag, f2.tag); assert_eq!(f1.ifd_num, f2.ifd_num); match f1.tag { - Tag::StripOffsets | Tag::TileOffsets | - Tag::JPEGInterchangeFormat => continue, - _ => {}, + Tag::StripOffsets | Tag::TileOffsets | Tag::JPEGInterchangeFormat => continue, + _ => {} } compare_field_value(&f1.value, &f2.value); } @@ -160,42 +162,37 @@ fn compare_field_value(value1: &Value, value2: &Value) { } // Compare other fields strictly. match (value1, value2) { - (&Value::Ascii(ref v1), &Value::Ascii(ref v2)) => - assert_eq!(v1, v2), + (&Value::Ascii(ref v1), &Value::Ascii(ref v2)) => assert_eq!(v1, v2), (&Value::Rational(ref v1), &Value::Rational(ref v2)) => { assert_eq!(v1.len(), v2.len()); for (r1, r2) in v1.iter().zip(v2.iter()) { assert_eq!(r1.num, r2.num); assert_eq!(r1.denom, r2.denom); } - }, - (&Value::SByte(ref v1), &Value::SByte(ref v2)) => - assert_eq!(v1, v2), - (&Value::Undefined(ref v1, _), &Value::Undefined(ref v2, _)) => - assert_eq!(v1, v2), - (&Value::SShort(ref v1), &Value::SShort(ref v2)) => - assert_eq!(v1, v2), - (&Value::SLong(ref v1), &Value::SLong(ref v2)) => - assert_eq!(v1, v2), + } + (&Value::SByte(ref v1), &Value::SByte(ref v2)) => assert_eq!(v1, v2), + (&Value::Undefined(ref v1, _), &Value::Undefined(ref v2, _)) => assert_eq!(v1, v2), + (&Value::SShort(ref v1), &Value::SShort(ref v2)) => assert_eq!(v1, v2), + (&Value::SLong(ref v1), &Value::SLong(ref v2)) => assert_eq!(v1, v2), (&Value::SRational(ref v1), &Value::SRational(ref v2)) => { assert_eq!(v1.len(), v2.len()); for (r1, r2) in v1.iter().zip(v2.iter()) { assert_eq!(r1.num, r2.num); assert_eq!(r1.denom, r2.denom); } - }, - (&Value::Float(ref v1), &Value::Float(ref v2)) => - assert_eq!(v1, v2), - (&Value::Double(ref v1), &Value::Double(ref v2)) => - assert_eq!(v1, v2), + } + (&Value::Float(ref v1), &Value::Float(ref v2)) => assert_eq!(v1, v2), + (&Value::Double(ref v1), &Value::Double(ref v2)) => assert_eq!(v1, v2), _ => panic!("{:?} != {:?}", value1, value2), } } -fn get_strips(exif: &Exif, ifd_num: In) -> Option> { - let offsets = exif.get_field(Tag::StripOffsets, ifd_num) +fn get_strips>(exif: &Exif, ifd_num: In) -> Option> { + let offsets = exif + .get_field(Tag::StripOffsets, ifd_num) .and_then(|f| f.value.iter_uint()); - let counts = exif.get_field(Tag::StripByteCounts, ifd_num) + let counts = exif + .get_field(Tag::StripByteCounts, ifd_num) .and_then(|f| f.value.iter_uint()); let (offsets, counts) = match (offsets, counts) { (Some(offsets), Some(counts)) => (offsets, counts), @@ -204,15 +201,19 @@ fn get_strips(exif: &Exif, ifd_num: In) -> Option> { }; let buf = exif.buf(); assert_eq!(offsets.len(), counts.len()); - let strips = offsets.zip(counts).map( - |(ofs, cnt)| &buf[ofs as usize .. (ofs + cnt) as usize]).collect(); + let strips = offsets + .zip(counts) + .map(|(ofs, cnt)| &buf[ofs as usize..(ofs + cnt) as usize]) + .collect(); Some(strips) } -fn get_tiles(exif: &Exif, ifd_num: In) -> Option> { - let offsets = exif.get_field(Tag::TileOffsets, ifd_num) +fn get_tiles>(exif: &Exif, ifd_num: In) -> Option> { + let offsets = exif + .get_field(Tag::TileOffsets, ifd_num) .and_then(|f| f.value.iter_uint()); - let counts = exif.get_field(Tag::TileByteCounts, ifd_num) + let counts = exif + .get_field(Tag::TileByteCounts, ifd_num) .and_then(|f| f.value.iter_uint()); let (offsets, counts) = match (offsets, counts) { (Some(offsets), Some(counts)) => (offsets, counts), @@ -221,15 +222,19 @@ fn get_tiles(exif: &Exif, ifd_num: In) -> Option> { }; assert_eq!(offsets.len(), counts.len()); let buf = exif.buf(); - let strips = offsets.zip(counts).map( - |(ofs, cnt)| &buf[ofs as usize .. (ofs + cnt) as usize]).collect(); + let strips = offsets + .zip(counts) + .map(|(ofs, cnt)| &buf[ofs as usize..(ofs + cnt) as usize]) + .collect(); Some(strips) } -fn get_jpeg(exif: &Exif, ifd_num: In) -> Option<&[u8]> { - let offset = exif.get_field(Tag::JPEGInterchangeFormat, ifd_num) +fn get_jpeg>(exif: &Exif, ifd_num: In) -> Option<&[u8]> { + let offset = exif + .get_field(Tag::JPEGInterchangeFormat, ifd_num) .and_then(|f| f.value.get_uint(0)); - let len = exif.get_field(Tag::JPEGInterchangeFormatLength, ifd_num) + let len = exif + .get_field(Tag::JPEGInterchangeFormatLength, ifd_num) .and_then(|f| f.value.get_uint(0)); let (offset, len) = match (offset, len) { (Some(offset), Some(len)) => (offset as usize, len as usize), @@ -237,5 +242,5 @@ fn get_jpeg(exif: &Exif, ifd_num: In) -> Option<&[u8]> { _ => panic!("inconsistent JPEG offset and length"), }; let buf = exif.buf(); - Some(&buf[offset..offset+len]) + Some(&buf[offset..offset + len]) }