2017-02-22 09:00:25 -05:00
|
|
|
//
|
|
|
|
// Copyright (c) 2017 KAMADA Ken'ichi.
|
|
|
|
// All rights reserved.
|
|
|
|
//
|
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
|
|
// modification, are permitted provided that the following conditions
|
|
|
|
// are met:
|
|
|
|
// 1. Redistributions of source code must retain the above copyright
|
|
|
|
// notice, this list of conditions and the following disclaimer.
|
|
|
|
// 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
|
|
// documentation and/or other materials provided with the distribution.
|
|
|
|
//
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
|
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
|
|
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
// SUCH DAMAGE.
|
|
|
|
//
|
|
|
|
|
2019-12-22 06:28:25 -05:00
|
|
|
use std::collections::HashMap;
|
2017-02-22 09:00:25 -05:00
|
|
|
use std::io;
|
|
|
|
use std::io::Read;
|
|
|
|
|
2019-04-01 08:11:54 -04:00
|
|
|
use crate::error::Error;
|
2020-01-19 06:58:01 -05:00
|
|
|
use crate::isobmff;
|
2019-04-01 08:11:54 -04:00
|
|
|
use crate::jpeg;
|
2020-08-03 09:57:50 -04:00
|
|
|
use crate::png;
|
2019-04-01 08:11:54 -04:00
|
|
|
use crate::tag::Tag;
|
|
|
|
use crate::tiff;
|
2019-12-14 07:57:56 -05:00
|
|
|
use crate::tiff::{Field, IfdEntry, In, ProvideUnit};
|
2020-12-14 07:12:19 -05:00
|
|
|
use crate::webp;
|
2017-02-22 09:00:25 -05:00
|
|
|
|
2020-01-26 09:12:47 -05:00
|
|
|
/// A struct to parse the Exif attributes and
|
|
|
|
/// create an `Exif` instance that holds the results.
|
2019-04-07 08:11:14 -04:00
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
/// ```
|
2020-02-01 06:28:21 -05:00
|
|
|
/// # use std::fmt::{Display, Formatter, Result};
|
|
|
|
/// # #[derive(Debug)] struct Error(&'static str);
|
|
|
|
/// # impl std::error::Error for Error {}
|
|
|
|
/// # impl Display for Error {
|
|
|
|
/// # fn fmt(&self, f: &mut Formatter) -> Result { f.write_str(self.0) }
|
|
|
|
/// # }
|
|
|
|
/// # fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
2019-04-20 10:16:01 -04:00
|
|
|
/// use exif::{In, Reader, Tag};
|
2020-02-01 06:28:21 -05:00
|
|
|
/// let file = std::fs::File::open("tests/exif.jpg")?;
|
|
|
|
/// let exif = Reader::new()
|
|
|
|
/// .read_from_container(&mut std::io::BufReader::new(&file))?;
|
|
|
|
/// let xres = exif.get_field(Tag::XResolution, In::PRIMARY)
|
|
|
|
/// .ok_or(Error("tests/exif.jpg must have XResolution"))?;
|
2020-01-20 08:05:06 -05:00
|
|
|
/// assert_eq!(xres.display_value().with_unit(&exif).to_string(),
|
2019-04-07 08:11:14 -04:00
|
|
|
/// "72 pixels per inch");
|
2020-02-01 06:28:21 -05:00
|
|
|
/// # Ok(()) }
|
2019-04-07 08:11:14 -04:00
|
|
|
/// ```
|
2017-02-22 09:00:25 -05:00
|
|
|
pub struct Reader {
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Reader {
|
2021-03-21 04:56:37 -04:00
|
|
|
/// Constructs a new `Reader`.
|
2020-01-20 08:05:06 -05:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {}
|
|
|
|
}
|
2017-02-22 09:00:25 -05:00
|
|
|
|
2020-01-20 08:05:06 -05:00
|
|
|
/// 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> {
|
|
|
|
let buf = data;
|
2019-12-14 07:57:56 -05:00
|
|
|
let (entries, le) = tiff::parse_exif(&buf)?;
|
2019-12-22 06:28:25 -05:00
|
|
|
let entry_map = entries.iter().enumerate()
|
|
|
|
.map(|(i, e)| (e.ifd_num_tag(), i)).collect();
|
2017-07-11 10:30:48 -04:00
|
|
|
|
2020-01-20 08:05:06 -05:00
|
|
|
Ok(Exif {
|
2017-07-11 10:30:48 -04:00
|
|
|
buf: buf,
|
2019-12-14 07:57:56 -05:00
|
|
|
entries: entries,
|
2019-12-22 06:28:25 -05:00
|
|
|
entry_map: entry_map,
|
2017-07-11 10:30:48 -04:00
|
|
|
little_endian: le,
|
|
|
|
})
|
2017-02-22 09:00:25 -05:00
|
|
|
}
|
|
|
|
|
2020-01-19 06:58:01 -05:00
|
|
|
/// Reads an image file and parses the Exif attributes in it.
|
|
|
|
/// If an error occurred, `exif::Error` is returned.
|
|
|
|
///
|
2020-01-26 09:12:47 -05:00
|
|
|
/// Supported formats are:
|
|
|
|
/// - TIFF and some RAW image formats based on it
|
|
|
|
/// - JPEG
|
|
|
|
/// - HEIF and coding-specific variations including HEIC and AVIF
|
2020-08-03 09:57:50 -04:00
|
|
|
/// - PNG
|
2020-12-14 07:12:19 -05:00
|
|
|
/// - WebP
|
2020-01-19 06:58:01 -05:00
|
|
|
///
|
|
|
|
/// This method is provided for the convenience even though
|
|
|
|
/// parsing containers is basically out of the scope of this library.
|
2020-01-20 08:05:06 -05:00
|
|
|
pub fn read_from_container<R>(&self, reader: &mut R) -> Result<Exif, Error>
|
2020-01-19 06:58:01 -05:00
|
|
|
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) {
|
|
|
|
reader.read_to_end(&mut buf)?;
|
|
|
|
} else if jpeg::is_jpeg(&buf) {
|
|
|
|
buf = jpeg::get_exif_attr(&mut buf.chain(reader))?;
|
2020-08-03 09:57:50 -04:00
|
|
|
} else if png::is_png(&buf) {
|
|
|
|
buf = png::get_exif_attr(&mut buf.chain(reader))?;
|
2020-01-19 06:58:01 -05:00
|
|
|
} else if isobmff::is_heif(&buf) {
|
|
|
|
reader.seek(io::SeekFrom::Start(0))?;
|
|
|
|
buf = isobmff::get_exif_attr(reader)?;
|
2020-12-14 07:12:19 -05:00
|
|
|
} else if webp::is_webp(&buf) {
|
|
|
|
buf = webp::get_exif_attr(&mut buf.chain(reader))?;
|
2020-01-19 06:58:01 -05:00
|
|
|
} else {
|
|
|
|
return Err(Error::InvalidFormat("Unknown image format"));
|
|
|
|
}
|
|
|
|
|
2020-02-29 05:10:41 -05:00
|
|
|
self.read_raw(buf)
|
2020-01-19 06:58:01 -05:00
|
|
|
}
|
2020-01-20 08:05:06 -05:00
|
|
|
}
|
|
|
|
|
2020-01-26 09:12:47 -05:00
|
|
|
/// A struct that holds the parsed Exif attributes.
|
2020-01-20 08:05:06 -05:00
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
/// ```
|
2020-02-01 06:28:21 -05:00
|
|
|
/// # fn main() { sub(); }
|
|
|
|
/// # fn sub() -> Option<()> {
|
2020-01-20 08:05:06 -05:00
|
|
|
/// # use exif::{In, Reader, Tag};
|
|
|
|
/// # let file = std::fs::File::open("tests/exif.jpg").unwrap();
|
|
|
|
/// # let exif = Reader::new().read_from_container(
|
|
|
|
/// # &mut std::io::BufReader::new(&file)).unwrap();
|
|
|
|
/// // Get a specific field.
|
2020-02-01 06:28:21 -05:00
|
|
|
/// let xres = exif.get_field(Tag::XResolution, In::PRIMARY)?;
|
2020-01-20 08:05:06 -05:00
|
|
|
/// assert_eq!(xres.display_value().with_unit(&exif).to_string(),
|
|
|
|
/// "72 pixels per inch");
|
|
|
|
/// // Iterate over all fields.
|
|
|
|
/// for f in exif.fields() {
|
|
|
|
/// println!("{} {} {}", f.tag, f.ifd_num, f.display_value());
|
|
|
|
/// }
|
2020-02-01 06:28:21 -05:00
|
|
|
/// # Some(()) }
|
2020-01-20 08:05:06 -05:00
|
|
|
/// ```
|
|
|
|
pub struct Exif {
|
|
|
|
// TIFF data.
|
|
|
|
buf: Vec<u8>,
|
|
|
|
// Exif fields. Vec is used to keep the ability to enumerate all fields
|
|
|
|
// even if there are duplicates.
|
|
|
|
entries: Vec<IfdEntry>,
|
|
|
|
// HashMap to the index of the Vec for faster random access.
|
|
|
|
entry_map: HashMap<(In, Tag), usize>,
|
|
|
|
// True if the TIFF data is little endian.
|
|
|
|
little_endian: bool,
|
|
|
|
}
|
2020-01-19 06:58:01 -05:00
|
|
|
|
2020-01-20 08:05:06 -05:00
|
|
|
impl Exif {
|
2017-02-22 09:00:25 -05:00
|
|
|
/// Returns the slice that contains the TIFF data.
|
|
|
|
#[inline]
|
|
|
|
pub fn buf(&self) -> &[u8] {
|
|
|
|
&self.buf[..]
|
|
|
|
}
|
|
|
|
|
2020-01-23 09:54:35 -05:00
|
|
|
/// Returns an iterator of Exif fields.
|
2017-02-22 09:00:25 -05:00
|
|
|
#[inline]
|
2019-12-22 07:08:20 -05:00
|
|
|
pub fn fields(&self) -> impl ExactSizeIterator<Item = &Field> {
|
2019-12-22 06:28:25 -05:00
|
|
|
self.entries.iter()
|
2019-12-14 07:57:56 -05:00
|
|
|
.map(move |e| e.ref_field(&self.buf, self.little_endian))
|
2017-02-22 09:00:25 -05:00
|
|
|
}
|
|
|
|
|
2020-01-26 09:12:47 -05:00
|
|
|
/// Returns true if the Exif data (TIFF structure) is in the
|
|
|
|
/// little-endian byte order.
|
2017-02-22 09:00:25 -05:00
|
|
|
#[inline]
|
|
|
|
pub fn little_endian(&self) -> bool {
|
|
|
|
self.little_endian
|
|
|
|
}
|
2017-07-11 10:30:48 -04:00
|
|
|
|
|
|
|
/// Returns a reference to the Exif field specified by the tag
|
2019-04-20 10:16:01 -04:00
|
|
|
/// and the IFD number.
|
2017-07-11 10:30:48 -04:00
|
|
|
#[inline]
|
2019-04-20 10:16:01 -04:00
|
|
|
pub fn get_field(&self, tag: Tag, ifd_num: In) -> Option<&Field> {
|
2019-12-22 06:28:25 -05:00
|
|
|
self.entry_map.get(&(ifd_num, tag))
|
|
|
|
.map(|&i| self.entries[i].ref_field(&self.buf, self.little_endian))
|
2017-07-11 10:30:48 -04:00
|
|
|
}
|
2017-02-22 09:00:25 -05:00
|
|
|
}
|
|
|
|
|
2020-01-20 08:05:06 -05:00
|
|
|
impl<'a> ProvideUnit<'a> for &'a Exif {
|
2019-12-14 07:57:35 -05:00
|
|
|
fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field> {
|
2019-04-20 10:16:01 -04:00
|
|
|
self.get_field(tag, ifd_num)
|
2019-04-07 08:11:14 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-22 09:00:25 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2017-07-11 10:30:48 -04:00
|
|
|
use std::fs::File;
|
2017-02-22 09:00:25 -05:00
|
|
|
use std::io::BufReader;
|
2021-05-04 08:42:26 -04:00
|
|
|
use crate::tag::Context;
|
2019-04-01 08:11:54 -04:00
|
|
|
use crate::value::Value;
|
2017-02-22 09:00:25 -05:00
|
|
|
use super::*;
|
|
|
|
|
2017-07-11 10:30:48 -04:00
|
|
|
#[test]
|
|
|
|
fn get_field() {
|
2019-05-08 10:09:03 -04:00
|
|
|
let file = File::open("tests/yaminabe.tif").unwrap();
|
2020-01-20 08:05:06 -05:00
|
|
|
let exif = Reader::new().read_from_container(
|
|
|
|
&mut BufReader::new(&file)).unwrap();
|
|
|
|
match exif.get_field(Tag::ImageDescription, In(0)).unwrap().value {
|
2019-04-20 10:16:01 -04:00
|
|
|
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test image"]),
|
|
|
|
ref v => panic!("wrong variant {:?}", v)
|
|
|
|
}
|
2020-01-20 08:05:06 -05:00
|
|
|
match exif.get_field(Tag::ImageDescription, In(1)).unwrap().value {
|
2019-04-20 10:16:01 -04:00
|
|
|
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test thumbnail"]),
|
|
|
|
ref v => panic!("wrong variant {:?}", v)
|
|
|
|
}
|
2020-01-20 08:05:06 -05:00
|
|
|
match exif.get_field(Tag::ImageDescription, In(2)).unwrap().value {
|
2019-05-08 10:09:03 -04:00
|
|
|
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test 2nd IFD"]),
|
|
|
|
ref v => panic!("wrong variant {:?}", v)
|
|
|
|
}
|
2017-07-11 10:30:48 -04:00
|
|
|
}
|
2019-04-07 08:11:14 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn display_value_with_unit() {
|
2019-05-08 10:09:03 -04:00
|
|
|
let file = File::open("tests/yaminabe.tif").unwrap();
|
2020-01-20 08:05:06 -05:00
|
|
|
let exif = Reader::new().read_from_container(
|
|
|
|
&mut BufReader::new(&file)).unwrap();
|
2019-04-07 08:11:14 -04:00
|
|
|
// No unit.
|
2020-01-20 08:05:06 -05:00
|
|
|
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
|
|
|
assert_eq!(exifver.display_value().with_unit(&exif).to_string(),
|
2019-04-07 08:11:14 -04:00
|
|
|
"2.31");
|
|
|
|
// Fixed string.
|
2020-01-20 08:05:06 -05:00
|
|
|
let width = exif.get_field(Tag::ImageWidth, In::PRIMARY).unwrap();
|
|
|
|
assert_eq!(width.display_value().with_unit(&exif).to_string(),
|
2019-05-08 10:09:03 -04:00
|
|
|
"17 pixels");
|
2019-04-07 08:11:14 -04:00
|
|
|
// Unit tag (with a non-default value).
|
2020-01-20 08:05:06 -05:00
|
|
|
let gpsalt = exif.get_field(Tag::GPSAltitude, In::PRIMARY).unwrap();
|
|
|
|
assert_eq!(gpsalt.display_value().with_unit(&exif).to_string(),
|
2019-04-07 08:11:14 -04:00
|
|
|
"0.5 meters below sea level");
|
|
|
|
// Unit tag is missing but the default is specified.
|
2020-01-20 08:05:06 -05:00
|
|
|
let xres = exif.get_field(Tag::XResolution, In::PRIMARY).unwrap();
|
|
|
|
assert_eq!(xres.display_value().with_unit(&exif).to_string(),
|
2019-04-07 08:11:14 -04:00
|
|
|
"72 pixels per inch");
|
|
|
|
// Unit tag is missing and the default is not specified.
|
2020-01-20 08:05:06 -05:00
|
|
|
let gpslat = exif.get_field(Tag::GPSLatitude, In::PRIMARY).unwrap();
|
|
|
|
assert_eq!(gpslat.display_value().with_unit(&exif).to_string(),
|
2019-04-07 08:11:14 -04:00
|
|
|
"10 deg 0 min 0 sec [GPSLatitudeRef missing]");
|
|
|
|
}
|
2020-01-19 06:58:01 -05:00
|
|
|
|
2021-05-04 08:42:26 -04:00
|
|
|
#[test]
|
|
|
|
fn yaminabe() {
|
|
|
|
let file = File::open("tests/yaminabe.tif").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();
|
|
|
|
assert!(!be.little_endian());
|
|
|
|
assert!(le.little_endian());
|
|
|
|
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");
|
|
|
|
let f = exif.get_field(Tag::Humidity, In(0)).unwrap();
|
|
|
|
assert_eq!(f.display_value().to_string(), "65");
|
|
|
|
let f = exif.get_field(Tag(Context::Tiff, 65000), In(0)).unwrap();
|
|
|
|
match f.value {
|
|
|
|
Value::Float(ref v) => assert_eq!(v[0], f32::MIN),
|
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
let f = exif.get_field(Tag(Context::Tiff, 65001), In(0)).unwrap();
|
|
|
|
match f.value {
|
|
|
|
Value::Double(ref v) => assert_eq!(v[0], f64::MIN),
|
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-19 06:58:01 -05:00
|
|
|
#[test]
|
|
|
|
fn heif() {
|
|
|
|
let file = std::fs::File::open("tests/exif.heic").unwrap();
|
2020-01-20 08:05:06 -05:00
|
|
|
let exif = Reader::new().read_from_container(
|
2020-01-19 06:58:01 -05:00
|
|
|
&mut std::io::BufReader::new(&file)).unwrap();
|
2020-01-20 08:05:06 -05:00
|
|
|
assert_eq!(exif.fields().len(), 2);
|
|
|
|
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
2020-01-19 06:58:01 -05:00
|
|
|
assert_eq!(exifver.display_value().to_string(), "2.31");
|
|
|
|
}
|
2020-08-03 09:57:50 -04:00
|
|
|
|
|
|
|
#[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();
|
|
|
|
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");
|
|
|
|
}
|
2020-12-14 07:12:19 -05:00
|
|
|
|
|
|
|
#[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();
|
|
|
|
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");
|
|
|
|
let desc = exif.get_field(Tag::ImageDescription, In::PRIMARY).unwrap();
|
|
|
|
assert_eq!(desc.display_value().to_string(), "\"WebP test\"");
|
|
|
|
}
|
2017-02-22 09:00:25 -05:00
|
|
|
}
|