Improve
This commit is contained in:
parent
8232c6cd51
commit
a6461ef7d5
|
@ -0,0 +1,2 @@
|
||||||
|
[build]
|
||||||
|
rustflags = ["--cfg", "unsound_local_offset"]
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
|
@ -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"
|
|
@ -15,3 +15,9 @@ license = "BSD-2-Clause"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mutate_once = "0.1.1"
|
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"]}
|
||||||
|
|
132
src/reader.rs
132
src/reader.rs
|
@ -59,8 +59,7 @@ use crate::webp;
|
||||||
/// "72 pixels per inch");
|
/// "72 pixels per inch");
|
||||||
/// # Ok(()) }
|
/// # Ok(()) }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Reader {
|
pub struct Reader {}
|
||||||
}
|
|
||||||
|
|
||||||
impl Reader {
|
impl Reader {
|
||||||
/// Constructs a new `Reader`.
|
/// Constructs a new `Reader`.
|
||||||
|
@ -70,11 +69,14 @@ impl Reader {
|
||||||
|
|
||||||
/// Parses the Exif attributes from raw Exif data.
|
/// Parses the Exif attributes from raw Exif data.
|
||||||
/// If an error occurred, `exif::Error` is returned.
|
/// 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 buf = data;
|
||||||
let (entries, le) = tiff::parse_exif(&buf)?;
|
let (entries, le) = tiff::parse_exif(buf.as_ref())?;
|
||||||
let entry_map = entries.iter().enumerate()
|
let entry_map = entries
|
||||||
.map(|(i, e)| (e.ifd_num_tag(), i)).collect();
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, e)| (e.ifd_num_tag(), i))
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(Exif {
|
Ok(Exif {
|
||||||
buf: buf,
|
buf: buf,
|
||||||
|
@ -96,8 +98,10 @@ impl Reader {
|
||||||
///
|
///
|
||||||
/// This method is provided for the convenience even though
|
/// This method is provided for the convenience even though
|
||||||
/// parsing containers is basically out of the scope of this library.
|
/// parsing containers is basically out of the scope of this library.
|
||||||
pub fn read_from_container<R>(&self, reader: &mut R) -> Result<Exif, Error>
|
pub fn read_from_container<'a, R>(&self, reader: &mut R) -> Result<Exif<Vec<u8>>, Error>
|
||||||
where R: io::BufRead + io::Seek {
|
where
|
||||||
|
R: io::BufRead + io::Seek,
|
||||||
|
{
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
reader.by_ref().take(4096).read_to_end(&mut buf)?;
|
reader.by_ref().take(4096).read_to_end(&mut buf)?;
|
||||||
if tiff::is_tiff(&buf) {
|
if tiff::is_tiff(&buf) {
|
||||||
|
@ -139,9 +143,8 @@ impl Reader {
|
||||||
/// }
|
/// }
|
||||||
/// # Some(()) }
|
/// # Some(()) }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Exif {
|
pub struct Exif<Buf: AsRef<[u8]>> {
|
||||||
// TIFF data.
|
buf: Buf,
|
||||||
buf: Vec<u8>,
|
|
||||||
// Exif fields. Vec is used to keep the ability to enumerate all fields
|
// Exif fields. Vec is used to keep the ability to enumerate all fields
|
||||||
// even if there are duplicates.
|
// even if there are duplicates.
|
||||||
entries: Vec<IfdEntry>,
|
entries: Vec<IfdEntry>,
|
||||||
|
@ -151,18 +154,32 @@ pub struct Exif {
|
||||||
little_endian: bool,
|
little_endian: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Exif {
|
impl<Buf: AsRef<[u8]>> Exif<Buf> {
|
||||||
/// Returns the slice that contains the TIFF data.
|
/// Returns the slice that contains the TIFF data.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn buf(&self) -> &[u8] {
|
pub fn buf(&self) -> &[u8] {
|
||||||
&self.buf[..]
|
self.buf.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator of Exif fields.
|
/// Returns an iterator of Exif fields.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fields(&self) -> impl ExactSizeIterator<Item = &Field> {
|
pub fn fields<'a>(&'a self) -> impl ExactSizeIterator<Item = &'a Field> {
|
||||||
self.entries.iter()
|
let buf = self.buf.as_ref();
|
||||||
.map(move |e| e.ref_field(&self.buf, self.little_endian))
|
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
|
/// Returns true if the Exif data (TIFF structure) is in the
|
||||||
|
@ -176,12 +193,13 @@ impl Exif {
|
||||||
/// and the IFD number.
|
/// and the IFD number.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_field(&self, tag: Tag, ifd_num: In) -> Option<&Field> {
|
pub fn get_field(&self, tag: Tag, ifd_num: In) -> Option<&Field> {
|
||||||
self.entry_map.get(&(ifd_num, tag))
|
self.entry_map
|
||||||
.map(|&i| self.entries[i].ref_field(&self.buf, self.little_endian))
|
.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> {
|
fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field> {
|
||||||
self.get_field(tag, ifd_num)
|
self.get_field(tag, ifd_num)
|
||||||
}
|
}
|
||||||
|
@ -189,69 +207,80 @@ impl<'a> ProvideUnit<'a> for &'a Exif {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::fs::File;
|
use super::*;
|
||||||
use std::io::BufReader;
|
|
||||||
use crate::tag::Context;
|
use crate::tag::Context;
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use super::*;
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_field() {
|
fn get_field() {
|
||||||
let file = File::open("tests/yaminabe.tif").unwrap();
|
let file = File::open("tests/yaminabe.tif").unwrap();
|
||||||
let exif = Reader::new().read_from_container(
|
let exif = Reader::new()
|
||||||
&mut BufReader::new(&file)).unwrap();
|
.read_from_container(&mut BufReader::new(&file))
|
||||||
|
.unwrap();
|
||||||
match exif.get_field(Tag::ImageDescription, In(0)).unwrap().value {
|
match exif.get_field(Tag::ImageDescription, In(0)).unwrap().value {
|
||||||
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test image"]),
|
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 {
|
match exif.get_field(Tag::ImageDescription, In(1)).unwrap().value {
|
||||||
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test thumbnail"]),
|
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 {
|
match exif.get_field(Tag::ImageDescription, In(2)).unwrap().value {
|
||||||
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test 2nd IFD"]),
|
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test 2nd IFD"]),
|
||||||
ref v => panic!("wrong variant {:?}", v)
|
ref v => panic!("wrong variant {:?}", v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_value_with_unit() {
|
fn display_value_with_unit() {
|
||||||
let file = File::open("tests/yaminabe.tif").unwrap();
|
let file = File::open("tests/yaminabe.tif").unwrap();
|
||||||
let exif = Reader::new().read_from_container(
|
let exif = Reader::new()
|
||||||
&mut BufReader::new(&file)).unwrap();
|
.read_from_container(&mut BufReader::new(&file))
|
||||||
|
.unwrap();
|
||||||
// No unit.
|
// No unit.
|
||||||
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
||||||
assert_eq!(exifver.display_value().with_unit(&exif).to_string(),
|
assert_eq!(exifver.display_value().with_unit(&exif).to_string(), "2.31");
|
||||||
"2.31");
|
|
||||||
// Fixed string.
|
// Fixed string.
|
||||||
let width = exif.get_field(Tag::ImageWidth, In::PRIMARY).unwrap();
|
let width = exif.get_field(Tag::ImageWidth, In::PRIMARY).unwrap();
|
||||||
assert_eq!(width.display_value().with_unit(&exif).to_string(),
|
assert_eq!(
|
||||||
"17 pixels");
|
width.display_value().with_unit(&exif).to_string(),
|
||||||
|
"17 pixels"
|
||||||
|
);
|
||||||
// Unit tag (with a non-default value).
|
// Unit tag (with a non-default value).
|
||||||
let gpsalt = exif.get_field(Tag::GPSAltitude, In::PRIMARY).unwrap();
|
let gpsalt = exif.get_field(Tag::GPSAltitude, In::PRIMARY).unwrap();
|
||||||
assert_eq!(gpsalt.display_value().with_unit(&exif).to_string(),
|
assert_eq!(
|
||||||
"0.5 meters below sea level");
|
gpsalt.display_value().with_unit(&exif).to_string(),
|
||||||
|
"0.5 meters below sea level"
|
||||||
|
);
|
||||||
// Unit tag is missing but the default is specified.
|
// Unit tag is missing but the default is specified.
|
||||||
let xres = exif.get_field(Tag::XResolution, In::PRIMARY).unwrap();
|
let xres = exif.get_field(Tag::XResolution, In::PRIMARY).unwrap();
|
||||||
assert_eq!(xres.display_value().with_unit(&exif).to_string(),
|
assert_eq!(
|
||||||
"72 pixels per inch");
|
xres.display_value().with_unit(&exif).to_string(),
|
||||||
|
"72 pixels per inch"
|
||||||
|
);
|
||||||
// Unit tag is missing and the default is not specified.
|
// Unit tag is missing and the default is not specified.
|
||||||
let gpslat = exif.get_field(Tag::GPSLatitude, In::PRIMARY).unwrap();
|
let gpslat = exif.get_field(Tag::GPSLatitude, In::PRIMARY).unwrap();
|
||||||
assert_eq!(gpslat.display_value().with_unit(&exif).to_string(),
|
assert_eq!(
|
||||||
"10 deg 0 min 0 sec [GPSLatitudeRef missing]");
|
gpslat.display_value().with_unit(&exif).to_string(),
|
||||||
|
"10 deg 0 min 0 sec [GPSLatitudeRef missing]"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn yaminabe() {
|
fn yaminabe() {
|
||||||
let file = File::open("tests/yaminabe.tif").unwrap();
|
let file = File::open("tests/yaminabe.tif").unwrap();
|
||||||
let be = Reader::new().read_from_container(
|
let be = Reader::new()
|
||||||
&mut BufReader::new(&file)).unwrap();
|
.read_from_container(&mut BufReader::new(&file))
|
||||||
|
.unwrap();
|
||||||
let file = File::open("tests/yaminale.tif").unwrap();
|
let file = File::open("tests/yaminale.tif").unwrap();
|
||||||
let le = Reader::new().read_from_container(
|
let le = Reader::new()
|
||||||
&mut BufReader::new(&file)).unwrap();
|
.read_from_container(&mut BufReader::new(&file))
|
||||||
|
.unwrap();
|
||||||
assert!(!be.little_endian());
|
assert!(!be.little_endian());
|
||||||
assert!(le.little_endian());
|
assert!(le.little_endian());
|
||||||
for exif in &[be, le] {
|
for exif in [be, le] {
|
||||||
assert_eq!(exif.fields().len(), 26);
|
assert_eq!(exif.fields().len(), 26);
|
||||||
let f = exif.get_field(Tag::ImageWidth, In(0)).unwrap();
|
let f = exif.get_field(Tag::ImageWidth, In(0)).unwrap();
|
||||||
assert_eq!(f.display_value().to_string(), "17");
|
assert_eq!(f.display_value().to_string(), "17");
|
||||||
|
@ -273,8 +302,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn heif() {
|
fn heif() {
|
||||||
let file = std::fs::File::open("tests/exif.heic").unwrap();
|
let file = std::fs::File::open("tests/exif.heic").unwrap();
|
||||||
let exif = Reader::new().read_from_container(
|
let exif = Reader::new()
|
||||||
&mut std::io::BufReader::new(&file)).unwrap();
|
.read_from_container(&mut std::io::BufReader::new(&file))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(exif.fields().len(), 2);
|
assert_eq!(exif.fields().len(), 2);
|
||||||
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
||||||
assert_eq!(exifver.display_value().to_string(), "2.31");
|
assert_eq!(exifver.display_value().to_string(), "2.31");
|
||||||
|
@ -283,8 +313,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn png() {
|
fn png() {
|
||||||
let file = std::fs::File::open("tests/exif.png").unwrap();
|
let file = std::fs::File::open("tests/exif.png").unwrap();
|
||||||
let exif = Reader::new().read_from_container(
|
let exif = Reader::new()
|
||||||
&mut std::io::BufReader::new(&file)).unwrap();
|
.read_from_container(&mut std::io::BufReader::new(&file))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(exif.fields().len(), 6);
|
assert_eq!(exif.fields().len(), 6);
|
||||||
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
||||||
assert_eq!(exifver.display_value().to_string(), "2.32");
|
assert_eq!(exifver.display_value().to_string(), "2.32");
|
||||||
|
@ -293,8 +324,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn webp() {
|
fn webp() {
|
||||||
let file = std::fs::File::open("tests/exif.webp").unwrap();
|
let file = std::fs::File::open("tests/exif.webp").unwrap();
|
||||||
let exif = Reader::new().read_from_container(
|
let exif = Reader::new()
|
||||||
&mut std::io::BufReader::new(&file)).unwrap();
|
.read_from_container(&mut std::io::BufReader::new(&file))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(exif.fields().len(), 6);
|
assert_eq!(exif.fields().len(), 6);
|
||||||
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
||||||
assert_eq!(exifver.display_value().to_string(), "2.32");
|
assert_eq!(exifver.display_value().to_string(), "2.32");
|
||||||
|
|
|
@ -56,6 +56,7 @@ use crate::util::atou16;
|
||||||
// emulate structural equivalency.
|
// emulate structural equivalency.
|
||||||
// <https://github.com/rust-lang/rfcs/pull/1445>
|
// <https://github.com/rust-lang/rfcs/pull/1445>
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[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);
|
pub struct Tag(pub Context, pub u16);
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
|
@ -114,6 +115,7 @@ impl fmt::Display for Tag {
|
||||||
|
|
||||||
/// An enum that indicates how a tag number is interpreted.
|
/// An enum that indicates how a tag number is interpreted.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Context {
|
pub enum Context {
|
||||||
/// TIFF attributes defined in the TIFF Rev. 6.0 specification.
|
/// TIFF attributes defined in the TIFF Rev. 6.0 specification.
|
||||||
|
|
336
src/tiff.rs
336
src/tiff.rs
|
@ -24,16 +24,16 @@
|
||||||
// SUCH DAMAGE.
|
// SUCH DAMAGE.
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use mutate_once::MutOnce;
|
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::error::Error;
|
||||||
use crate::tag::{Context, Tag, UnitPiece};
|
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::util::{atou16, ctou32};
|
||||||
|
use crate::value;
|
||||||
|
use crate::value::get_type_info;
|
||||||
|
use crate::value::Value;
|
||||||
|
|
||||||
// TIFF header magic numbers [EXIF23 4.5.2].
|
// TIFF header magic numbers [EXIF23 4.5.2].
|
||||||
const TIFF_BE: u16 = 0x4d4d;
|
const TIFF_BE: u16 = 0x4d4d;
|
||||||
|
@ -68,7 +68,7 @@ impl IfdEntry {
|
||||||
self.field.get_ref()
|
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.parse(data, le);
|
||||||
self.field.into_inner()
|
self.field.into_inner()
|
||||||
}
|
}
|
||||||
|
@ -85,21 +85,25 @@ impl IfdEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts a partially parsed value into a real one.
|
// 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 {
|
match *value {
|
||||||
Value::Unknown(typ, cnt, ofs) => {
|
Value::Unknown(typ, cnt, ofs) => {
|
||||||
let (unitlen, parser) = get_type_info::<E>(typ);
|
let (unitlen, parser) = get_type_info::<E>(typ);
|
||||||
if unitlen != 0 {
|
if unitlen != 0 {
|
||||||
*value = parser(data, ofs as usize, cnt as usize);
|
*value = parser(data, ofs as usize, cnt as usize);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => panic!("value is already parsed"),
|
_ => panic!("value is already parsed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A TIFF/Exif field.
|
/// A TIFF/Exif field.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
/// The tag of this field.
|
/// The tag of this field.
|
||||||
pub tag: Tag,
|
pub tag: Tag,
|
||||||
|
@ -121,7 +125,8 @@ pub struct Field {
|
||||||
/// assert_eq!(In::PRIMARY.index(), 0);
|
/// assert_eq!(In::PRIMARY.index(), 0);
|
||||||
/// assert_eq!(In::THUMBNAIL.index(), 1);
|
/// 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);
|
pub struct In(pub u16);
|
||||||
|
|
||||||
impl In {
|
impl In {
|
||||||
|
@ -152,8 +157,10 @@ impl fmt::Display for In {
|
||||||
/// If an error occurred, `exif::Error` is returned.
|
/// If an error occurred, `exif::Error` is returned.
|
||||||
pub fn parse_exif_compat03(data: &[u8]) -> Result<(Vec<Field>, bool), Error> {
|
pub fn parse_exif_compat03(data: &[u8]) -> Result<(Vec<Field>, bool), Error> {
|
||||||
parse_exif(data).map(|(entries, le)| {
|
parse_exif(data).map(|(entries, le)| {
|
||||||
let fields = entries.into_iter()
|
let fields = entries
|
||||||
.map(|e| e.into_field(data, le)).collect();
|
.into_iter()
|
||||||
|
.map(|e| e.into_field(data, le))
|
||||||
|
.collect();
|
||||||
(fields, le)
|
(fields, le)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -170,8 +177,10 @@ pub fn parse_exif(data: &[u8]) -> Result<(Vec<IfdEntry>, bool), Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_exif_sub<E>(data: &[u8])
|
fn parse_exif_sub<E>(data: &[u8]) -> Result<Vec<IfdEntry>, Error>
|
||||||
-> Result<Vec<IfdEntry>, Error> where E: Endian {
|
where
|
||||||
|
E: Endian,
|
||||||
|
{
|
||||||
// Parse the rest of the header (42 and the IFD offset).
|
// Parse the rest of the header (42 and the IFD offset).
|
||||||
if E::loadu16(data, 2) != TIFF_FORTY_TWO {
|
if E::loadu16(data, 2) != TIFF_FORTY_TWO {
|
||||||
return Err(Error::InvalidFormat("Invalid forty two"));
|
return Err(Error::InvalidFormat("Invalid forty two"));
|
||||||
|
@ -186,17 +195,23 @@ fn parse_exif_sub<E>(data: &[u8])
|
||||||
if ifd_num >= 8 {
|
if ifd_num >= 8 {
|
||||||
return Err(Error::InvalidFormat("Limit the IFD count to 8"));
|
return Err(Error::InvalidFormat("Limit the IFD count to 8"));
|
||||||
}
|
}
|
||||||
ifd_offset = parse_ifd::<E>(
|
ifd_offset = parse_ifd::<E>(&mut entries, data, ifd_offset, Context::Tiff, ifd_num)?;
|
||||||
&mut entries, data, ifd_offset, Context::Tiff, ifd_num)?;
|
|
||||||
ifd_num_ck = ifd_num.checked_add(1);
|
ifd_num_ck = ifd_num.checked_add(1);
|
||||||
}
|
}
|
||||||
Ok(entries)
|
Ok(entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse IFD [EXIF23 4.6.2].
|
// Parse IFD [EXIF23 4.6.2].
|
||||||
fn parse_ifd<E>(entries: &mut Vec<IfdEntry>, data: &[u8],
|
fn parse_ifd<E>(
|
||||||
offset: usize, ctx: Context, ifd_num: u16)
|
entries: &mut Vec<IfdEntry>,
|
||||||
-> Result<usize, Error> where E: Endian {
|
data: &[u8],
|
||||||
|
offset: usize,
|
||||||
|
ctx: Context,
|
||||||
|
ifd_num: u16,
|
||||||
|
) -> Result<usize, Error>
|
||||||
|
where
|
||||||
|
E: Endian,
|
||||||
|
{
|
||||||
// Count (the number of the entries).
|
// Count (the number of the entries).
|
||||||
if data.len() < offset || data.len() - offset < 2 {
|
if data.len() < offset || data.len() - offset < 2 {
|
||||||
return Err(Error::InvalidFormat("Truncated IFD count"));
|
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 cnt = E::loadu32(data, offset + 2 + i * 12 + 4);
|
||||||
let valofs_at = offset + 2 + i * 12 + 8;
|
let valofs_at = offset + 2 + i * 12 + 8;
|
||||||
let (unitlen, _parser) = get_type_info::<E>(typ);
|
let (unitlen, _parser) = get_type_info::<E>(typ);
|
||||||
let vallen = unitlen.checked_mul(cnt as usize).ok_or(
|
let vallen = unitlen
|
||||||
Error::InvalidFormat("Invalid entry count"))?;
|
.checked_mul(cnt as usize)
|
||||||
|
.ok_or(Error::InvalidFormat("Invalid entry count"))?;
|
||||||
let mut val = if vallen <= 4 {
|
let mut val = if vallen <= 4 {
|
||||||
Value::Unknown(typ, cnt, valofs_at as u32)
|
Value::Unknown(typ, cnt, valofs_at as u32)
|
||||||
} else {
|
} else {
|
||||||
|
@ -229,14 +245,23 @@ fn parse_ifd<E>(entries: &mut Vec<IfdEntry>, data: &[u8],
|
||||||
// recursively defined.
|
// recursively defined.
|
||||||
let tag = Tag(ctx, tag);
|
let tag = Tag(ctx, tag);
|
||||||
match tag {
|
match tag {
|
||||||
Tag::ExifIFDPointer => parse_child_ifd::<E>(
|
Tag::ExifIFDPointer => {
|
||||||
entries, data, &mut val, Context::Exif, ifd_num)?,
|
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::GPSInfoIFDPointer => {
|
||||||
Tag::InteropIFDPointer => parse_child_ifd::<E>(
|
parse_child_ifd::<E>(entries, data, &mut val, Context::Gps, ifd_num)?
|
||||||
entries, data, &mut val, Context::Interop, ifd_num)?,
|
}
|
||||||
_ => entries.push(IfdEntry { field: Field {
|
Tag::InteropIFDPointer => {
|
||||||
tag: tag, ifd_num: In(ifd_num), value: val }.into()}),
|
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)
|
Ok(next_ifd_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_child_ifd<E>(entries: &mut Vec<IfdEntry>, data: &[u8],
|
fn parse_child_ifd<E>(
|
||||||
pointer: &mut Value, ctx: Context, ifd_num: u16)
|
entries: &mut Vec<IfdEntry>,
|
||||||
-> Result<(), Error> where E: Endian {
|
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.
|
// The pointer is not yet parsed, so do it here.
|
||||||
IfdEntry::parse_value::<E>(pointer, data);
|
IfdEntry::parse_value::<E>(pointer, data);
|
||||||
|
|
||||||
// A pointer field has type == LONG and count == 1, so the
|
// A pointer field has type == LONG and count == 1, so the
|
||||||
// value (IFD offset) must be embedded in the "value offset"
|
// value (IFD offset) must be embedded in the "value offset"
|
||||||
// element of the field.
|
// element of the field.
|
||||||
let ofs = pointer.get_uint(0).ok_or(
|
let ofs = pointer
|
||||||
Error::InvalidFormat("Invalid pointer"))? as usize;
|
.get_uint(0)
|
||||||
|
.ok_or(Error::InvalidFormat("Invalid pointer"))? as usize;
|
||||||
match parse_ifd::<E>(entries, data, ofs, ctx, ifd_num)? {
|
match parse_ifd::<E>(entries, data, ofs, ctx, ifd_num)? {
|
||||||
0 => Ok(()),
|
0 => Ok(()),
|
||||||
_ => Err(Error::InvalidFormat("Unexpected next IFD")),
|
_ => 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");
|
/// assert_eq!(dt.to_string(), "2016-05-04 03:02:01");
|
||||||
/// # Ok(()) }
|
/// # Ok(()) }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct DateTime {
|
pub struct DateTime {
|
||||||
pub year: u16,
|
pub year: u16,
|
||||||
pub month: u8,
|
pub month: u8,
|
||||||
|
@ -305,8 +339,12 @@ impl DateTime {
|
||||||
return Err(Error::BlankValue("DateTime is blank"));
|
return Err(Error::BlankValue("DateTime is blank"));
|
||||||
} else if data.len() < 19 {
|
} else if data.len() < 19 {
|
||||||
return Err(Error::InvalidFormat("DateTime too short"));
|
return Err(Error::InvalidFormat("DateTime too short"));
|
||||||
} else if !(data[4] == b':' && data[7] == b':' && data[10] == b' ' &&
|
} else if !(data[4] == b':'
|
||||||
data[13] == b':' && data[16] == b':') {
|
&& data[7] == b':'
|
||||||
|
&& data[10] == b' '
|
||||||
|
&& data[13] == b':'
|
||||||
|
&& data[16] == b':')
|
||||||
|
{
|
||||||
return Err(Error::InvalidFormat("Invalid DateTime delimiter"));
|
return Err(Error::InvalidFormat("Invalid DateTime delimiter"));
|
||||||
}
|
}
|
||||||
Ok(DateTime {
|
Ok(DateTime {
|
||||||
|
@ -365,13 +403,103 @@ impl DateTime {
|
||||||
});
|
});
|
||||||
Ok(())
|
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 {
|
impl fmt::Display for DateTime {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
|
write!(
|
||||||
self.year, self.month, self.day,
|
f,
|
||||||
self.hour, self.minute, self.second)
|
"{: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> {
|
impl<'a> DisplayValue<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_unit<T>(&self, unit_provider: T)
|
pub fn with_unit<T>(&self, unit_provider: T) -> DisplayValueUnit<'a, T>
|
||||||
-> DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
|
where
|
||||||
|
T: ProvideUnit<'a>,
|
||||||
|
{
|
||||||
DisplayValueUnit {
|
DisplayValueUnit {
|
||||||
ifd_num: self.ifd_num,
|
ifd_num: self.ifd_num,
|
||||||
value_display: self.value_display,
|
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.
|
/// 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,
|
ifd_num: In,
|
||||||
value_display: value::Display<'a>,
|
value_display: value::Display<'a>,
|
||||||
unit: Option<&'static [UnitPiece]>,
|
unit: Option<&'static [UnitPiece]>,
|
||||||
unit_provider: T,
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
if let Some(unit) = self.unit {
|
if let Some(unit) = self.unit {
|
||||||
assert!(!unit.is_empty());
|
assert!(!unit.is_empty());
|
||||||
|
@ -476,15 +612,15 @@ impl<'a, T> fmt::Display for DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
|
||||||
match *piece {
|
match *piece {
|
||||||
UnitPiece::Value => self.value_display.fmt(f),
|
UnitPiece::Value => self.value_display.fmt(f),
|
||||||
UnitPiece::Str(s) => f.write_str(s),
|
UnitPiece::Str(s) => f.write_str(s),
|
||||||
UnitPiece::Tag(tag) =>
|
UnitPiece::Tag(tag) => {
|
||||||
if let Some(x) = self.unit_provider.get_field(
|
if let Some(x) = self.unit_provider.get_field(tag, self.ifd_num) {
|
||||||
tag, self.ifd_num) {
|
|
||||||
x.value.display_as(tag).fmt(f)
|
x.value.display_as(tag).fmt(f)
|
||||||
} else if let Some(x) = tag.default_value() {
|
} else if let Some(x) = tag.default_value() {
|
||||||
x.display_as(tag).fmt(f)
|
x.display_as(tag).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "[{} missing]", tag)
|
write!(f, "[{} missing]", tag)
|
||||||
},
|
}
|
||||||
|
}
|
||||||
}?
|
}?
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -533,9 +669,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn truncated() {
|
fn truncated() {
|
||||||
let mut data =
|
let mut data = b"MM\0\x2a\0\0\0\x08\
|
||||||
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"
|
||||||
\0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\0".to_vec();
|
.to_vec();
|
||||||
parse_exif(&data).unwrap();
|
parse_exif(&data).unwrap();
|
||||||
while let Some(_) = data.pop() {
|
while let Some(_) = data.pop() {
|
||||||
parse_exif(&data).unwrap_err();
|
parse_exif(&data).unwrap_err();
|
||||||
|
@ -548,8 +684,10 @@ mod tests {
|
||||||
fn inf_loop_by_next() {
|
fn inf_loop_by_next() {
|
||||||
let data = b"MM\0\x2a\0\0\0\x08\
|
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";
|
\0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\x08";
|
||||||
assert_err_pat!(parse_exif(data),
|
assert_err_pat!(
|
||||||
Error::InvalidFormat("Limit the IFD count to 8"));
|
parse_exif(data),
|
||||||
|
Error::InvalidFormat("Limit the IFD count to 8")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -559,8 +697,10 @@ mod tests {
|
||||||
\x00\x00\x00\x00\
|
\x00\x00\x00\x00\
|
||||||
\x00\x01\x90\x00\x00\x07\x00\x00\x00\x040231\
|
\x00\x01\x90\x00\x00\x07\x00\x00\x00\x040231\
|
||||||
\x00\x00\x00\x08";
|
\x00\x00\x00\x08";
|
||||||
assert_err_pat!(parse_exif(data),
|
assert_err_pat!(
|
||||||
Error::InvalidFormat("Unexpected next IFD"));
|
parse_exif(data),
|
||||||
|
Error::InvalidFormat("Unexpected next IFD")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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";
|
\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();
|
let (v, le) = parse_exif(data).unwrap();
|
||||||
assert_eq!(v.len(), 1);
|
assert_eq!(v.len(), 1);
|
||||||
assert_pat!(v[0].ref_field(data, le).value,
|
assert_pat!(
|
||||||
Value::Unknown(0xffff, 1, 0x12));
|
v[0].ref_field(data, le).value,
|
||||||
|
Value::Unknown(0xffff, 1, 0x12)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -610,6 +752,23 @@ mod tests {
|
||||||
assert_err_pat!(dt.parse_offset(b" "), Error::BlankValue(_));
|
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]
|
#[test]
|
||||||
fn display_value_with_unit() {
|
fn display_value_with_unit() {
|
||||||
let cm = Field {
|
let cm = Field {
|
||||||
|
@ -628,24 +787,24 @@ mod tests {
|
||||||
ifd_num: In::PRIMARY,
|
ifd_num: In::PRIMARY,
|
||||||
value: Value::Undefined(b"0231".to_vec(), 0),
|
value: Value::Undefined(b"0231".to_vec(), 0),
|
||||||
};
|
};
|
||||||
assert_eq!(exifver.display_value().to_string(),
|
assert_eq!(exifver.display_value().to_string(), "2.31");
|
||||||
"2.31");
|
assert_eq!(exifver.display_value().with_unit(()).to_string(), "2.31");
|
||||||
assert_eq!(exifver.display_value().with_unit(()).to_string(),
|
assert_eq!(exifver.display_value().with_unit(&cm).to_string(), "2.31");
|
||||||
"2.31");
|
|
||||||
assert_eq!(exifver.display_value().with_unit(&cm).to_string(),
|
|
||||||
"2.31");
|
|
||||||
// Fixed string.
|
// Fixed string.
|
||||||
let width = Field {
|
let width = Field {
|
||||||
tag: Tag::ImageWidth,
|
tag: Tag::ImageWidth,
|
||||||
ifd_num: In::PRIMARY,
|
ifd_num: In::PRIMARY,
|
||||||
value: Value::Short(vec![257]),
|
value: Value::Short(vec![257]),
|
||||||
};
|
};
|
||||||
assert_eq!(width.display_value().to_string(),
|
assert_eq!(width.display_value().to_string(), "257");
|
||||||
"257");
|
assert_eq!(
|
||||||
assert_eq!(width.display_value().with_unit(()).to_string(),
|
width.display_value().with_unit(()).to_string(),
|
||||||
"257 pixels");
|
"257 pixels"
|
||||||
assert_eq!(width.display_value().with_unit(&cm).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 (with a non-default value).
|
||||||
// Unit tag is missing but the default is specified.
|
// Unit tag is missing but the default is specified.
|
||||||
let xres = Field {
|
let xres = Field {
|
||||||
|
@ -653,27 +812,34 @@ mod tests {
|
||||||
ifd_num: In::PRIMARY,
|
ifd_num: In::PRIMARY,
|
||||||
value: Value::Rational(vec![(300, 1).into()]),
|
value: Value::Rational(vec![(300, 1).into()]),
|
||||||
};
|
};
|
||||||
assert_eq!(xres.display_value().to_string(),
|
assert_eq!(xres.display_value().to_string(), "300");
|
||||||
"300");
|
assert_eq!(
|
||||||
assert_eq!(xres.display_value().with_unit(()).to_string(),
|
xres.display_value().with_unit(()).to_string(),
|
||||||
"300 pixels per inch");
|
"300 pixels per inch"
|
||||||
assert_eq!(xres.display_value().with_unit(&cm).to_string(),
|
);
|
||||||
"300 pixels per cm");
|
assert_eq!(
|
||||||
assert_eq!(xres.display_value().with_unit(&cm_tn).to_string(),
|
xres.display_value().with_unit(&cm).to_string(),
|
||||||
"300 pixels per inch");
|
"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.
|
// Unit tag is missing and the default is not specified.
|
||||||
let gpslat = Field {
|
let gpslat = Field {
|
||||||
tag: Tag::GPSLatitude,
|
tag: Tag::GPSLatitude,
|
||||||
ifd_num: In::PRIMARY,
|
ifd_num: In::PRIMARY,
|
||||||
value: Value::Rational(vec![
|
value: Value::Rational(vec![(10, 1).into(), (0, 1).into(), (1, 10).into()]),
|
||||||
(10, 1).into(), (0, 1).into(), (1, 10).into()]),
|
|
||||||
};
|
};
|
||||||
assert_eq!(gpslat.display_value().to_string(),
|
assert_eq!(gpslat.display_value().to_string(), "10 deg 0 min 0.1 sec");
|
||||||
"10 deg 0 min 0.1 sec");
|
assert_eq!(
|
||||||
assert_eq!(gpslat.display_value().with_unit(()).to_string(),
|
gpslat.display_value().with_unit(()).to_string(),
|
||||||
"10 deg 0 min 0.1 sec [GPSLatitudeRef missing]");
|
"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().with_unit(&cm).to_string(),
|
||||||
|
"10 deg 0 min 0.1 sec [GPSLatitudeRef missing]"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
17
src/value.rs
17
src/value.rs
|
@ -30,7 +30,8 @@ use std::fmt::Write as _;
|
||||||
use crate::endian::Endian;
|
use crate::endian::Endian;
|
||||||
|
|
||||||
/// A type and values of a TIFF/Exif field.
|
/// 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 {
|
pub enum Value {
|
||||||
/// Vector of 8-bit unsigned integers.
|
/// Vector of 8-bit unsigned integers.
|
||||||
Byte(Vec<u8>),
|
Byte(Vec<u8>),
|
||||||
|
@ -96,7 +97,7 @@ impl Value {
|
||||||
|
|
||||||
/// Returns the value as a slice if the type is BYTE.
|
/// Returns the value as a slice if the type is BYTE.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn byte(&self) -> Option<&[u8]> {
|
pub fn byte(&self) -> Option<&[u8]> {
|
||||||
match *self {
|
match *self {
|
||||||
Value::Byte(ref v) => Some(v),
|
Value::Byte(ref v) => Some(v),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -105,7 +106,7 @@ impl Value {
|
||||||
|
|
||||||
/// Returns the value as `AsciiValues` if the type is ASCII.
|
/// Returns the value as `AsciiValues` if the type is ASCII.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn ascii(&self) -> Option<AsciiValues> {
|
pub fn ascii(&self) -> Option<AsciiValues> {
|
||||||
match *self {
|
match *self {
|
||||||
Value::Ascii(ref v) => Some(AsciiValues(v)),
|
Value::Ascii(ref v) => Some(AsciiValues(v)),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -114,7 +115,7 @@ impl Value {
|
||||||
|
|
||||||
/// Returns the value as a slice if the type is RATIONAL.
|
/// Returns the value as a slice if the type is RATIONAL.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn rational(&self) -> Option<&[Rational]> {
|
pub fn rational(&self) -> Option<&[Rational]> {
|
||||||
match *self {
|
match *self {
|
||||||
Value::Rational(ref v) => Some(v),
|
Value::Rational(ref v) => Some(v),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -123,7 +124,7 @@ impl Value {
|
||||||
|
|
||||||
/// Returns the value as a slice if the type is UNDEFINED.
|
/// Returns the value as a slice if the type is UNDEFINED.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn undefined(&self) -> Option<&[u8]> {
|
pub fn undefined(&self) -> Option<&[u8]> {
|
||||||
match *self {
|
match *self {
|
||||||
Value::Undefined(ref v, _) => Some(v),
|
Value::Undefined(ref v, _) => Some(v),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -296,7 +297,8 @@ impl From<&DefaultValue> for Option<Value> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An unsigned rational number, which is a pair of 32-bit unsigned integers.
|
/// 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 }
|
pub struct Rational { pub num: u32, pub denom: u32 }
|
||||||
|
|
||||||
impl Rational {
|
impl Rational {
|
||||||
|
@ -346,7 +348,8 @@ impl From<Rational> for f32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A signed rational number, which is a pair of 32-bit signed integers.
|
/// 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 }
|
pub struct SRational { pub num: i32, pub denom: i32 }
|
||||||
|
|
||||||
impl SRational {
|
impl SRational {
|
||||||
|
|
|
@ -35,10 +35,10 @@ use std::fs::File;
|
||||||
use std::io::{BufReader, Cursor};
|
use std::io::{BufReader, Cursor};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use exif::experimental::Writer;
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
use exif::Error;
|
use exif::Error;
|
||||||
use exif::{Exif, In, Reader, Value, Tag};
|
use exif::{Exif, In, Reader, Tag, Value};
|
||||||
use exif::experimental::Writer;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exif_heic() {
|
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();
|
let path = path.as_ref();
|
||||||
|
|
||||||
// Read.
|
// Read.
|
||||||
|
@ -85,7 +88,7 @@ fn rwr_compare<P>(path: P) where P: AsRef<Path> {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{}: {}: Skipped", path.display(), e);
|
println!("{}: {}: Skipped", path.display(), e);
|
||||||
return;
|
return;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
let strips = get_strips(&exif1, In::PRIMARY);
|
let strips = get_strips(&exif1, In::PRIMARY);
|
||||||
let tn_strips = get_strips(&exif1, In::THUMBNAIL);
|
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();
|
writer.write(&mut out, exif1.little_endian()).unwrap();
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
match writer.write(&mut out, exif1.little_endian()) {
|
match writer.write(&mut out, exif1.little_endian()) {
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
Err(Error::NotSupported(_)) => {
|
Err(Error::NotSupported(_)) => {
|
||||||
println!("{}: Contains unknown type", path.display());
|
println!("{}: Contains unknown type", path.display());
|
||||||
return;
|
return;
|
||||||
},
|
}
|
||||||
e => e.unwrap(),
|
e => e.unwrap(),
|
||||||
}
|
}
|
||||||
let out = out.into_inner();
|
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.tag, f2.tag);
|
||||||
assert_eq!(f1.ifd_num, f2.ifd_num);
|
assert_eq!(f1.ifd_num, f2.ifd_num);
|
||||||
match f1.tag {
|
match f1.tag {
|
||||||
Tag::StripOffsets | Tag::TileOffsets |
|
Tag::StripOffsets | Tag::TileOffsets | Tag::JPEGInterchangeFormat => continue,
|
||||||
Tag::JPEGInterchangeFormat => continue,
|
_ => {}
|
||||||
_ => {},
|
|
||||||
}
|
}
|
||||||
compare_field_value(&f1.value, &f2.value);
|
compare_field_value(&f1.value, &f2.value);
|
||||||
}
|
}
|
||||||
|
@ -160,42 +162,37 @@ fn compare_field_value(value1: &Value, value2: &Value) {
|
||||||
}
|
}
|
||||||
// Compare other fields strictly.
|
// Compare other fields strictly.
|
||||||
match (value1, value2) {
|
match (value1, value2) {
|
||||||
(&Value::Ascii(ref v1), &Value::Ascii(ref v2)) =>
|
(&Value::Ascii(ref v1), &Value::Ascii(ref v2)) => assert_eq!(v1, v2),
|
||||||
assert_eq!(v1, v2),
|
|
||||||
(&Value::Rational(ref v1), &Value::Rational(ref v2)) => {
|
(&Value::Rational(ref v1), &Value::Rational(ref v2)) => {
|
||||||
assert_eq!(v1.len(), v2.len());
|
assert_eq!(v1.len(), v2.len());
|
||||||
for (r1, r2) in v1.iter().zip(v2.iter()) {
|
for (r1, r2) in v1.iter().zip(v2.iter()) {
|
||||||
assert_eq!(r1.num, r2.num);
|
assert_eq!(r1.num, r2.num);
|
||||||
assert_eq!(r1.denom, r2.denom);
|
assert_eq!(r1.denom, r2.denom);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
(&Value::SByte(ref v1), &Value::SByte(ref v2)) =>
|
(&Value::SByte(ref v1), &Value::SByte(ref v2)) => assert_eq!(v1, v2),
|
||||||
assert_eq!(v1, v2),
|
(&Value::Undefined(ref v1, _), &Value::Undefined(ref v2, _)) => assert_eq!(v1, v2),
|
||||||
(&Value::Undefined(ref v1, _), &Value::Undefined(ref v2, _)) =>
|
(&Value::SShort(ref v1), &Value::SShort(ref v2)) => assert_eq!(v1, v2),
|
||||||
assert_eq!(v1, v2),
|
(&Value::SLong(ref v1), &Value::SLong(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)) => {
|
(&Value::SRational(ref v1), &Value::SRational(ref v2)) => {
|
||||||
assert_eq!(v1.len(), v2.len());
|
assert_eq!(v1.len(), v2.len());
|
||||||
for (r1, r2) in v1.iter().zip(v2.iter()) {
|
for (r1, r2) in v1.iter().zip(v2.iter()) {
|
||||||
assert_eq!(r1.num, r2.num);
|
assert_eq!(r1.num, r2.num);
|
||||||
assert_eq!(r1.denom, r2.denom);
|
assert_eq!(r1.denom, r2.denom);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
(&Value::Float(ref v1), &Value::Float(ref v2)) =>
|
(&Value::Float(ref v1), &Value::Float(ref v2)) => assert_eq!(v1, v2),
|
||||||
assert_eq!(v1, v2),
|
(&Value::Double(ref v1), &Value::Double(ref v2)) => assert_eq!(v1, v2),
|
||||||
(&Value::Double(ref v1), &Value::Double(ref v2)) =>
|
|
||||||
assert_eq!(v1, v2),
|
|
||||||
_ => panic!("{:?} != {:?}", value1, value2),
|
_ => panic!("{:?} != {:?}", value1, value2),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_strips(exif: &Exif, ifd_num: In) -> Option<Vec<&[u8]>> {
|
fn get_strips<Buf: AsRef<[u8]>>(exif: &Exif<Buf>, ifd_num: In) -> Option<Vec<&[u8]>> {
|
||||||
let offsets = exif.get_field(Tag::StripOffsets, ifd_num)
|
let offsets = exif
|
||||||
|
.get_field(Tag::StripOffsets, ifd_num)
|
||||||
.and_then(|f| f.value.iter_uint());
|
.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());
|
.and_then(|f| f.value.iter_uint());
|
||||||
let (offsets, counts) = match (offsets, counts) {
|
let (offsets, counts) = match (offsets, counts) {
|
||||||
(Some(offsets), Some(counts)) => (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();
|
let buf = exif.buf();
|
||||||
assert_eq!(offsets.len(), counts.len());
|
assert_eq!(offsets.len(), counts.len());
|
||||||
let strips = offsets.zip(counts).map(
|
let strips = offsets
|
||||||
|(ofs, cnt)| &buf[ofs as usize .. (ofs + cnt) as usize]).collect();
|
.zip(counts)
|
||||||
|
.map(|(ofs, cnt)| &buf[ofs as usize..(ofs + cnt) as usize])
|
||||||
|
.collect();
|
||||||
Some(strips)
|
Some(strips)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tiles(exif: &Exif, ifd_num: In) -> Option<Vec<&[u8]>> {
|
fn get_tiles<Buf: AsRef<[u8]>>(exif: &Exif<Buf>, ifd_num: In) -> Option<Vec<&[u8]>> {
|
||||||
let offsets = exif.get_field(Tag::TileOffsets, ifd_num)
|
let offsets = exif
|
||||||
|
.get_field(Tag::TileOffsets, ifd_num)
|
||||||
.and_then(|f| f.value.iter_uint());
|
.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());
|
.and_then(|f| f.value.iter_uint());
|
||||||
let (offsets, counts) = match (offsets, counts) {
|
let (offsets, counts) = match (offsets, counts) {
|
||||||
(Some(offsets), Some(counts)) => (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());
|
assert_eq!(offsets.len(), counts.len());
|
||||||
let buf = exif.buf();
|
let buf = exif.buf();
|
||||||
let strips = offsets.zip(counts).map(
|
let strips = offsets
|
||||||
|(ofs, cnt)| &buf[ofs as usize .. (ofs + cnt) as usize]).collect();
|
.zip(counts)
|
||||||
|
.map(|(ofs, cnt)| &buf[ofs as usize..(ofs + cnt) as usize])
|
||||||
|
.collect();
|
||||||
Some(strips)
|
Some(strips)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_jpeg(exif: &Exif, ifd_num: In) -> Option<&[u8]> {
|
fn get_jpeg<Buf: AsRef<[u8]>>(exif: &Exif<Buf>, ifd_num: In) -> Option<&[u8]> {
|
||||||
let offset = exif.get_field(Tag::JPEGInterchangeFormat, ifd_num)
|
let offset = exif
|
||||||
|
.get_field(Tag::JPEGInterchangeFormat, ifd_num)
|
||||||
.and_then(|f| f.value.get_uint(0));
|
.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));
|
.and_then(|f| f.value.get_uint(0));
|
||||||
let (offset, len) = match (offset, len) {
|
let (offset, len) = match (offset, len) {
|
||||||
(Some(offset), Some(len)) => (offset as usize, len as usize),
|
(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"),
|
_ => panic!("inconsistent JPEG offset and length"),
|
||||||
};
|
};
|
||||||
let buf = exif.buf();
|
let buf = exif.buf();
|
||||||
Some(&buf[offset..offset+len])
|
Some(&buf[offset..offset + len])
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue