This commit is contained in:
Michael Pfaff 2021-05-28 13:48:05 -04:00
parent 8232c6cd51
commit a6461ef7d5
Signed by: michael
GPG Key ID: E53B118B12B5C7F9
9 changed files with 539 additions and 181 deletions

2
.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[build]
rustflags = ["--cfg", "unsound_local_offset"]

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

141
Cargo.lock generated Normal file
View File

@ -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"

View File

@ -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"]}

View File

@ -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<u8>) -> Result<Exif, Error> {
pub fn read_raw<Buf: AsRef<[u8]>>(&self, data: Buf) -> Result<Exif<Buf>, 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<R>(&self, reader: &mut R) -> Result<Exif, Error>
where R: io::BufRead + io::Seek {
pub fn read_from_container<'a, R>(&self, reader: &mut R) -> Result<Exif<Vec<u8>>, 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<u8>,
pub struct Exif<Buf: AsRef<[u8]>> {
buf: Buf,
// Exif fields. Vec is used to keep the ability to enumerate all fields
// even if there are duplicates.
entries: Vec<IfdEntry>,
@ -151,18 +154,32 @@ pub struct Exif {
little_endian: bool,
}
impl Exif {
impl<Buf: AsRef<[u8]>> Exif<Buf> {
/// 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<Item = &Field> {
self.entries.iter()
.map(move |e| e.ref_field(&self.buf, self.little_endian))
pub fn fields<'a>(&'a self) -> impl ExactSizeIterator<Item = &'a Field> {
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<Field> {
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<Buf> {
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");

View File

@ -56,6 +56,7 @@ use crate::util::atou16;
// emulate structural equivalency.
// <https://github.com/rust-lang/rfcs/pull/1445>
#[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.

View File

@ -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<E>(value: &mut Value, data: &[u8]) where E: Endian {
fn parse_value<E>(value: &mut Value, data: &[u8])
where
E: Endian,
{
match *value {
Value::Unknown(typ, cnt, ofs) => {
let (unitlen, parser) = get_type_info::<E>(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<Field>, 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<IfdEntry>, bool), Error> {
}
}
fn parse_exif_sub<E>(data: &[u8])
-> Result<Vec<IfdEntry>, Error> where E: Endian {
fn parse_exif_sub<E>(data: &[u8]) -> Result<Vec<IfdEntry>, 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<E>(data: &[u8])
if ifd_num >= 8 {
return Err(Error::InvalidFormat("Limit the IFD count to 8"));
}
ifd_offset = parse_ifd::<E>(
&mut entries, data, ifd_offset, Context::Tiff, ifd_num)?;
ifd_offset = parse_ifd::<E>(&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<E>(entries: &mut Vec<IfdEntry>, data: &[u8],
offset: usize, ctx: Context, ifd_num: u16)
-> Result<usize, Error> where E: Endian {
fn parse_ifd<E>(
entries: &mut Vec<IfdEntry>,
data: &[u8],
offset: usize,
ctx: Context,
ifd_num: u16,
) -> Result<usize, Error>
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<E>(entries: &mut Vec<IfdEntry>, 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::<E>(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<E>(entries: &mut Vec<IfdEntry>, data: &[u8],
// recursively defined.
let tag = Tag(ctx, tag);
match tag {
Tag::ExifIFDPointer => parse_child_ifd::<E>(
entries, data, &mut val, Context::Exif, ifd_num)?,
Tag::GPSInfoIFDPointer => parse_child_ifd::<E>(
entries, data, &mut val, Context::Gps, ifd_num)?,
Tag::InteropIFDPointer => parse_child_ifd::<E>(
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::<E>(entries, data, &mut val, Context::Exif, ifd_num)?
}
Tag::GPSInfoIFDPointer => {
parse_child_ifd::<E>(entries, data, &mut val, Context::Gps, ifd_num)?
}
Tag::InteropIFDPointer => {
parse_child_ifd::<E>(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<E>(entries: &mut Vec<IfdEntry>, data: &[u8],
Ok(next_ifd_offset)
}
fn parse_child_ifd<E>(entries: &mut Vec<IfdEntry>, data: &[u8],
pointer: &mut Value, ctx: Context, ifd_num: u16)
-> Result<(), Error> where E: Endian {
fn parse_child_ifd<E>(
entries: &mut Vec<IfdEntry>,
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::<E>(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::<E>(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<Self, (Error, Self)> {
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<Self, (Error, Self)> {
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<DateTime> for time::OffsetDateTime {
// TODO fill in a type
type Error = ();
fn try_from(value: DateTime) -> core::result::Result<Self, Self::Error> {
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<Self, Self::Error> {
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<T>(&self, unit_provider: T)
-> DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
pub fn with_unit<T>(&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]

View File

@ -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<u8>),
@ -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<AsciiValues> {
pub fn ascii(&self) -> Option<AsciiValues> {
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<Value> {
}
/// 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<Rational> 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 {

View File

@ -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<P>(path: P) where P: AsRef<Path> {
fn rwr_compare<P>(path: P)
where
P: AsRef<Path>,
{
let path = path.as_ref();
// Read.
@ -85,7 +88,7 @@ fn rwr_compare<P>(path: P) where P: AsRef<Path> {
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<P>(path: P) where P: AsRef<Path> {
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<P>(path: P) where P: AsRef<Path> {
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<Vec<&[u8]>> {
let offsets = exif.get_field(Tag::StripOffsets, ifd_num)
fn get_strips<Buf: AsRef<[u8]>>(exif: &Exif<Buf>, ifd_num: In) -> Option<Vec<&[u8]>> {
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<Vec<&[u8]>> {
};
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<Vec<&[u8]>> {
let offsets = exif.get_field(Tag::TileOffsets, ifd_num)
fn get_tiles<Buf: AsRef<[u8]>>(exif: &Exif<Buf>, ifd_num: In) -> Option<Vec<&[u8]>> {
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<Vec<&[u8]>> {
};
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<Buf: AsRef<[u8]>>(exif: &Exif<Buf>, 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])
}