Separate struct Exif from Reader.

This commit is contained in:
KAMADA Ken'ichi 2020-01-20 22:05:06 +09:00
parent ffabb8adc2
commit 1568e3151f
6 changed files with 123 additions and 104 deletions

View File

@ -42,14 +42,14 @@ fn main() {
fn dump_file(path: &Path) -> Result<(), exif::Error> {
let file = File::open(path)?;
let reader = exif::Reader::read_from_container(
let exif = exif::Reader::new().read_from_container(
&mut BufReader::new(&file))?;
println!("{}", path.display());
for f in reader.fields() {
for f in exif.fields() {
println!(" {}/{}: {}",
f.ifd_num.index(), f.tag,
f.display_value().with_unit(&reader));
f.display_value().with_unit(&exif));
if let exif::Value::Ascii(ref v) = f.value {
println!(" Ascii({:?})",
v.iter().map(|x| escape(x)).collect::<Vec<_>>());

View File

@ -33,7 +33,8 @@ use exif::{DateTime, In, Reader, Value, Tag};
fn main() {
let file = File::open("tests/exif.jpg").unwrap();
let reader = Reader::new(&mut BufReader::new(&file)).unwrap();
let exif = Reader::new().read_from_container(
&mut BufReader::new(&file)).unwrap();
// To obtain a string representation, `Value::display_as`
// or `Field::display_value` can be used. To display a value with its
@ -44,15 +45,15 @@ fn main() {
Tag::ImageDescription,
Tag::DateTime];
for &tag in tag_list.iter() {
if let Some(field) = reader.get_field(tag, In::PRIMARY) {
if let Some(field) = exif.get_field(tag, In::PRIMARY) {
println!("{}: {}",
field.tag, field.display_value().with_unit(&reader));
field.tag, field.display_value().with_unit(&exif));
}
}
// To get unsigned integer value(s) from either of BYTE, SHORT,
// or LONG, `Value::get_uint` or `Value::iter_uint` can be used.
if let Some(field) = reader.get_field(Tag::PixelXDimension, In::PRIMARY) {
if let Some(field) = exif.get_field(Tag::PixelXDimension, In::PRIMARY) {
if let Some(width) = field.value.get_uint(0) {
println!("Valid width of the image is {}.", width);
}
@ -60,7 +61,7 @@ fn main() {
// To convert a Rational or SRational to an f64, `Rational::to_f64`
// or `SRational::to_f64` can be used.
if let Some(field) = reader.get_field(Tag::XResolution, In::PRIMARY) {
if let Some(field) = exif.get_field(Tag::XResolution, In::PRIMARY) {
match field.value {
Value::Rational(ref vec) if !vec.is_empty() =>
println!("X resolution is {}.", vec[0].to_f64()),
@ -69,7 +70,7 @@ fn main() {
}
// To parse a DateTime-like field, `DateTime::from_ascii` can be used.
if let Some(field) = reader.get_field(Tag::DateTime, In::PRIMARY) {
if let Some(field) = exif.get_field(Tag::DateTime, In::PRIMARY) {
match field.value {
Value::Ascii(ref vec) if !vec.is_empty() => {
if let Ok(datetime) = DateTime::from_ascii(&vec[0]) {

View File

@ -35,11 +35,11 @@
//! ```
//! for path in &["tests/exif.jpg", "tests/exif.tif"] {
//! let file = std::fs::File::open(path).unwrap();
//! let reader = exif::Reader::new(
//! let exif = exif::Reader::new().read_from_container(
//! &mut std::io::BufReader::new(&file)).unwrap();
//! for f in reader.fields() {
//! for f in exif.fields() {
//! println!("{} {} {}",
//! f.tag, f.ifd_num, f.display_value().with_unit(&reader));
//! f.tag, f.ifd_num, f.display_value().with_unit(&exif));
//! }
//! }
//! ```
@ -54,7 +54,7 @@
//! reader.get_field(Tag::DateTime, false)
//! ```
//! The new code in 0.4.x:
//! ```
//! ```ignore
//! # use exif::{In, Reader, Tag};
//! # let file = std::fs::File::open("tests/exif.tif").unwrap();
//! # let reader = Reader::new(
@ -64,7 +64,7 @@
//! ```
//! As an additional feature, access to the 2nd or further IFD,
//! which some RAW formats may have, is also possible in 0.4.x:
//! ```
//! ```ignore
//! # use exif::{In, Reader, Tag};
//! # let file = std::fs::File::open("tests/exif.tif").unwrap();
//! # let reader = Reader::new(
@ -84,9 +84,9 @@
//! ```
//! # use exif::{In, Reader};
//! # let file = std::fs::File::open("tests/exif.tif").unwrap();
//! # let reader = Reader::new(
//! # let exif = Reader::new().read_from_container(
//! # &mut std::io::BufReader::new(&file)).unwrap();
//! # let field = reader.fields().next().unwrap();
//! # let field = exif.fields().next().unwrap();
//! match field.ifd_num {
//! In::PRIMARY => {}, // for the primary image
//! In::THUMBNAIL => {}, // for the thumbnail image
@ -105,14 +105,14 @@
//! It is usually handier than `Value::display_as`.
//! ```
//! # let file = std::fs::File::open("tests/exif.tif").unwrap();
//! # let reader = exif::Reader::new(
//! # let exif = exif::Reader::new().read_from_container(
//! # &mut std::io::BufReader::new(&file)).unwrap();
//! # let field = reader.fields().next().unwrap();
//! # let field = exif.fields().next().unwrap();
//! assert_eq!(field.display_value().to_string(),
//! field.value.display_as(field.tag).to_string());
//! ```
//! * Displaying a value with its unit is supported.
//! ```
//! ```ignore
//! # let file = std::fs::File::open("tests/exif.tif").unwrap();
//! # let reader = exif::Reader::new(
//! # &mut std::io::BufReader::new(&file)).unwrap();
@ -129,7 +129,7 @@
pub use error::Error;
pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg;
pub use reader::Reader;
pub use reader::{Exif, Reader};
pub use tag::{Context, Tag};
pub use tiff::{DateTime, Field, In};
pub use tiff::parse_exif_compat03 as parse_exif;

View File

@ -35,53 +35,37 @@ use crate::tag::Tag;
use crate::tiff;
use crate::tiff::{Field, IfdEntry, In, ProvideUnit};
/// The `Reader` struct reads a JPEG or TIFF image,
/// parses the Exif attributes in it, and holds the results.
/// The `Reader` struct parses the Exif attributes and
/// returns `Exif` struct that holds the results.
///
/// # Examples
/// ```
/// use exif::{In, Reader, Tag};
/// let file = std::fs::File::open("tests/exif.jpg").unwrap();
/// let reader = Reader::new(&mut std::io::BufReader::new(&file)).unwrap();
/// let xres = reader.get_field(Tag::XResolution, In::PRIMARY).unwrap();
/// assert_eq!(xres.display_value().with_unit(&reader).to_string(),
/// let exif = Reader::new().read_from_container(
/// &mut std::io::BufReader::new(&file)).unwrap();
/// let xres = exif.get_field(Tag::XResolution, In::PRIMARY).unwrap();
/// assert_eq!(xres.display_value().with_unit(&exif).to_string(),
/// "72 pixels per inch");
/// ```
pub struct Reader {
// 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,
}
impl Reader {
/// Reads a JPEG or TIFF image and parses the Exif attributes in it.
/// If an error occurred, `exif::Error` is returned.
pub fn new<R>(reader: &mut R)
-> Result<Reader, Error> where R: io::BufRead {
// Parse the data.
let mut buf = Vec::new();
reader.by_ref().take(4).read_to_end(&mut buf)?;
if jpeg::is_jpeg(&buf) {
let exif_buf = jpeg::get_exif_attr(
&mut buf.as_mut_slice().chain(reader))?;
buf = exif_buf;
} else if tiff::is_tiff(&buf) {
reader.read_to_end(&mut buf)?;
} else {
return Err(Error::InvalidFormat("Unknown image format"));
}
/// Construct a new `Reader`.
pub fn new() -> Self {
Self {}
}
/// 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;
let (entries, le) = tiff::parse_exif(&buf)?;
let entry_map = entries.iter().enumerate()
.map(|(i, e)| (e.ifd_num_tag(), i)).collect();
Ok(Reader {
Ok(Exif {
buf: buf,
entries: entries,
entry_map: entry_map,
@ -96,7 +80,7 @@ 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>(reader: &mut R) -> Result<Reader, Error>
pub fn read_from_container<R>(&self, reader: &mut R) -> Result<Exif, Error>
where R: io::BufRead + io::Seek {
let mut buf = Vec::new();
reader.by_ref().take(4096).read_to_end(&mut buf)?;
@ -115,14 +99,45 @@ impl Reader {
let entry_map = entries.iter().enumerate()
.map(|(i, e)| (e.ifd_num_tag(), i)).collect();
Ok(Reader {
Ok(Exif {
buf: buf,
entries: entries,
entry_map: entry_map,
little_endian: le,
})
}
}
/// The `Exif` struct holds the parsed Exif attributes.
///
/// # Examples
/// ```
/// # 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.
/// let xres = exif.get_field(Tag::XResolution, In::PRIMARY).unwrap();
/// 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());
/// }
/// ```
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,
}
impl Exif {
/// Returns the slice that contains the TIFF data.
#[inline]
pub fn buf(&self) -> &[u8] {
@ -151,7 +166,7 @@ impl Reader {
}
}
impl<'a> ProvideUnit<'a> for &'a Reader {
impl<'a> ProvideUnit<'a> for &'a Exif {
fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field> {
self.get_field(tag, ifd_num)
}
@ -167,16 +182,17 @@ mod tests {
#[test]
fn get_field() {
let file = File::open("tests/yaminabe.tif").unwrap();
let reader = Reader::new(&mut BufReader::new(&file)).unwrap();
match reader.get_field(Tag::ImageDescription, In(0)).unwrap().value {
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)
}
match reader.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"]),
ref v => panic!("wrong variant {:?}", v)
}
match reader.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"]),
ref v => panic!("wrong variant {:?}", v)
}
@ -185,36 +201,37 @@ mod tests {
#[test]
fn display_value_with_unit() {
let file = File::open("tests/yaminabe.tif").unwrap();
let reader = Reader::new(&mut BufReader::new(&file)).unwrap();
let exif = Reader::new().read_from_container(
&mut BufReader::new(&file)).unwrap();
// No unit.
let exifver = reader.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
assert_eq!(exifver.display_value().with_unit(&reader).to_string(),
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
assert_eq!(exifver.display_value().with_unit(&exif).to_string(),
"2.31");
// Fixed string.
let width = reader.get_field(Tag::ImageWidth, In::PRIMARY).unwrap();
assert_eq!(width.display_value().with_unit(&reader).to_string(),
let width = exif.get_field(Tag::ImageWidth, In::PRIMARY).unwrap();
assert_eq!(width.display_value().with_unit(&exif).to_string(),
"17 pixels");
// Unit tag (with a non-default value).
let gpsalt = reader.get_field(Tag::GPSAltitude, In::PRIMARY).unwrap();
assert_eq!(gpsalt.display_value().with_unit(&reader).to_string(),
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");
// Unit tag is missing but the default is specified.
let xres = reader.get_field(Tag::XResolution, In::PRIMARY).unwrap();
assert_eq!(xres.display_value().with_unit(&reader).to_string(),
let xres = exif.get_field(Tag::XResolution, In::PRIMARY).unwrap();
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 = reader.get_field(Tag::GPSLatitude, In::PRIMARY).unwrap();
assert_eq!(gpslat.display_value().with_unit(&reader).to_string(),
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]");
}
#[test]
fn heif() {
let file = std::fs::File::open("tests/exif.heic").unwrap();
let reader = Reader::read_from_container(
let exif = Reader::new().read_from_container(
&mut std::io::BufReader::new(&file)).unwrap();
assert_eq!(reader.fields().len(), 2);
let exifver = reader.get_field(Tag::ExifVersion, In::PRIMARY).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");
}
}

View File

@ -379,9 +379,9 @@ impl Field {
///
/// To print the value with the unit, call `with_unit` method on the
/// returned object. It takes a parameter, which is either `()`,
/// `&Field`, or `&Reader` and provides the unit information.
/// `&Field`, or `&Exif` and provides the unit information.
/// If the unit does not depend on another field, `()` can be used.
/// Otherwise, `&Field` or `&Reader` should be used.
/// Otherwise, `&Field` or `&Exif` should be used.
///
/// # Examples
///

View File

@ -37,7 +37,7 @@ use std::path::Path;
#[cfg(not(test))]
use exif::Error;
use exif::{In, Reader, Value, Tag};
use exif::{Exif, In, Reader, Value, Tag};
use exif::experimental::Writer;
#[test]
@ -61,24 +61,25 @@ fn rwr_compare<P>(path: P) where P: AsRef<Path> {
// Read.
let file = File::open(path).unwrap();
let mut bufreader = BufReader::new(&file);
#[cfg(test)]
let reader1 = Reader::new(&mut BufReader::new(&file)).unwrap();
let exif1 = Reader::new().read_from_container(&mut bufreader).unwrap();
#[cfg(not(test))]
let reader1 = match Reader::new(&mut BufReader::new(&file)) {
Ok(reader) => reader,
let exif1 = match Reader::new().read_from_container(&mut bufreader) {
Ok(exif) => exif,
Err(e) => {
println!("{}: {}: Skipped", path.display(), e);
return;
},
};
let strips = get_strips(&reader1, In::PRIMARY);
let tn_strips = get_strips(&reader1, In::THUMBNAIL);
let tiles = get_tiles(&reader1, In::PRIMARY);
let tn_jpeg = get_jpeg(&reader1, In::THUMBNAIL);
let strips = get_strips(&exif1, In::PRIMARY);
let tn_strips = get_strips(&exif1, In::THUMBNAIL);
let tiles = get_tiles(&exif1, In::PRIMARY);
let tn_jpeg = get_jpeg(&exif1, In::THUMBNAIL);
// Write.
let mut writer = Writer::new();
for f in reader1.fields() {
for f in exif1.fields() {
writer.push_field(f);
}
if let Some(ref strips) = strips {
@ -95,9 +96,9 @@ fn rwr_compare<P>(path: P) where P: AsRef<Path> {
}
let mut out = Cursor::new(Vec::new());
#[cfg(test)]
writer.write(&mut out, reader1.little_endian()).unwrap();
writer.write(&mut out, exif1.little_endian()).unwrap();
#[cfg(not(test))]
match writer.write(&mut out, reader1.little_endian()) {
match writer.write(&mut out, exif1.little_endian()) {
Ok(_) => {},
Err(Error::NotSupported(_)) => {
println!("{}: Contains unknown type", path.display());
@ -108,12 +109,12 @@ fn rwr_compare<P>(path: P) where P: AsRef<Path> {
let out = out.into_inner();
// Re-read.
let reader2 = Reader::new(&mut &out[..]).unwrap();
let exif2 = Reader::new().read_raw(out).unwrap();
// Sort the fields (some files have wrong tag order).
let mut fields1 = reader1.fields().collect::<Vec<_>>();
let mut fields1 = exif1.fields().collect::<Vec<_>>();
fields1.sort_by_key(|f| (f.ifd_num, f.tag));
let mut fields2 = reader2.fields().collect::<Vec<_>>();
let mut fields2 = exif2.fields().collect::<Vec<_>>();
fields2.sort_by_key(|f| (f.ifd_num, f.tag));
// Compare.
@ -128,10 +129,10 @@ fn rwr_compare<P>(path: P) where P: AsRef<Path> {
}
compare_field_value(&f1.value, &f2.value);
}
assert_eq!(get_strips(&reader2, In::PRIMARY), strips);
assert_eq!(get_strips(&reader2, In::THUMBNAIL), tn_strips);
assert_eq!(get_tiles(&reader2, In::PRIMARY), tiles);
assert_eq!(get_jpeg(&reader2, In::THUMBNAIL), tn_jpeg);
assert_eq!(get_strips(&exif2, In::PRIMARY), strips);
assert_eq!(get_strips(&exif2, In::THUMBNAIL), tn_strips);
assert_eq!(get_tiles(&exif2, In::PRIMARY), tiles);
assert_eq!(get_jpeg(&exif2, In::THUMBNAIL), tn_jpeg);
}
// Compare field values.
@ -176,27 +177,27 @@ fn compare_field_value(value1: &Value, value2: &Value) {
}
}
fn get_strips(reader: &Reader, ifd_num: In) -> Option<Vec<&[u8]>> {
let offsets = reader.get_field(Tag::StripOffsets, ifd_num)
fn get_strips(exif: &Exif, ifd_num: In) -> Option<Vec<&[u8]>> {
let offsets = exif.get_field(Tag::StripOffsets, ifd_num)
.and_then(|f| f.value.iter_uint());
let counts = reader.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),
(None, None) => return None,
_ => panic!("inconsistent strip offsets and byte counts"),
};
let buf = reader.buf();
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();
Some(strips)
}
fn get_tiles(reader: &Reader, ifd_num: In) -> Option<Vec<&[u8]>> {
let offsets = reader.get_field(Tag::TileOffsets, ifd_num)
fn get_tiles(exif: &Exif, ifd_num: In) -> Option<Vec<&[u8]>> {
let offsets = exif.get_field(Tag::TileOffsets, ifd_num)
.and_then(|f| f.value.iter_uint());
let counts = reader.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),
@ -204,22 +205,22 @@ fn get_tiles(reader: &Reader, ifd_num: In) -> Option<Vec<&[u8]>> {
_ => panic!("inconsistent tile offsets and byte counts"),
};
assert_eq!(offsets.len(), counts.len());
let buf = reader.buf();
let buf = exif.buf();
let strips = offsets.zip(counts).map(
|(ofs, cnt)| &buf[ofs as usize .. (ofs + cnt) as usize]).collect();
Some(strips)
}
fn get_jpeg(reader: &Reader, ifd_num: In) -> Option<&[u8]> {
let offset = reader.get_field(Tag::JPEGInterchangeFormat, ifd_num)
fn get_jpeg(exif: &Exif, ifd_num: In) -> Option<&[u8]> {
let offset = exif.get_field(Tag::JPEGInterchangeFormat, ifd_num)
.and_then(|f| f.value.get_uint(0));
let len = reader.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),
(None, None) => return None,
_ => panic!("inconsistent JPEG offset and length"),
};
let buf = reader.buf();
let buf = exif.buf();
Some(&buf[offset..offset+len])
}