diff --git a/examples/dumpexif.rs b/examples/dumpexif.rs index d18e187..fb0e203 100644 --- a/examples/dumpexif.rs +++ b/examples/dumpexif.rs @@ -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::>()); diff --git a/examples/reading.rs b/examples/reading.rs index d21eed2..f3ae6d9 100644 --- a/examples/reading.rs +++ b/examples/reading.rs @@ -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]) { diff --git a/src/lib.rs b/src/lib.rs index 50bdea4..18cf4b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/reader.rs b/src/reader.rs index 17a78fb..dad64d3 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -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, - // Exif fields. Vec is used to keep the ability to enumerate all fields - // even if there are duplicates. - entries: Vec, - // 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(reader: &mut R) - -> Result 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) -> Result { + 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(reader: &mut R) -> Result + pub fn read_from_container(&self, reader: &mut R) -> Result 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, + // Exif fields. Vec is used to keep the ability to enumerate all fields + // even if there are duplicates. + entries: Vec, + // 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"); } } diff --git a/src/tiff.rs b/src/tiff.rs index 773d663..a474111 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -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 /// diff --git a/tests/rwrcmp.rs b/tests/rwrcmp.rs index 121cb72..d49cab0 100644 --- a/tests/rwrcmp.rs +++ b/tests/rwrcmp.rs @@ -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

(path: P) where P: AsRef { // 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

(path: P) where P: AsRef { } 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

(path: P) where P: AsRef { 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::>(); + let mut fields1 = exif1.fields().collect::>(); fields1.sort_by_key(|f| (f.ifd_num, f.tag)); - let mut fields2 = reader2.fields().collect::>(); + let mut fields2 = exif2.fields().collect::>(); fields2.sort_by_key(|f| (f.ifd_num, f.tag)); // Compare. @@ -128,10 +129,10 @@ fn rwr_compare

(path: P) where P: AsRef { } 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> { - let offsets = reader.get_field(Tag::StripOffsets, ifd_num) +fn get_strips(exif: &Exif, ifd_num: In) -> Option> { + let offsets = exif.get_field(Tag::StripOffsets, ifd_num) .and_then(|f| f.value.iter_uint()); - let counts = 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> { - let offsets = reader.get_field(Tag::TileOffsets, ifd_num) +fn get_tiles(exif: &Exif, ifd_num: In) -> Option> { + let offsets = exif.get_field(Tag::TileOffsets, ifd_num) .and_then(|f| f.value.iter_uint()); - let counts = 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> { _ => 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]) }