kamadak-exif-0.5.5/.cargo_vcs_info.json0000644000000001360000000000100133650ustar { "git": { "sha1": "67ec4446a8ddfcfe86ac393248d778eb93a38e29" }, "path_in_vcs": "" }kamadak-exif-0.5.5/Cargo.lock0000644000000006020000000000100113360ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "kamadak-exif" version = "0.5.5" dependencies = [ "mutate_once", ] [[package]] name = "mutate_once" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" kamadak-exif-0.5.5/Cargo.toml0000644000000020650000000000100113660ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "kamadak-exif" version = "0.5.5" authors = ["KAMADA Ken'ichi "] description = "Exif parsing library written in pure Rust" homepage = "https://github.com/kamadak/exif-rs" documentation = "https://docs.rs/kamadak-exif" readme = "README" keywords = [ "Exif", "JPEG", "parser", "reader", "TIFF", ] categories = [ "multimedia::encoding", "multimedia::images", "parser-implementations", ] license = "BSD-2-Clause" repository = "https://github.com/kamadak/exif-rs" [lib] name = "exif" [dependencies.mutate_once] version = "0.1.1" kamadak-exif-0.5.5/Cargo.toml.orig000064400000000000000000000010651046102023000150460ustar 00000000000000[package] name = "kamadak-exif" version = "0.5.5" authors = ["KAMADA Ken'ichi "] edition = "2018" description = "Exif parsing library written in pure Rust" documentation = "https://docs.rs/kamadak-exif" homepage = "https://github.com/kamadak/exif-rs" repository = "https://github.com/kamadak/exif-rs" readme = "README" keywords = ["Exif", "JPEG", "parser", "reader", "TIFF"] categories = ["multimedia::encoding", "multimedia::images", "parser-implementations"] license = "BSD-2-Clause" [lib] name = "exif" [dependencies] mutate_once = "0.1.1" kamadak-exif-0.5.5/NEWS000064400000000000000000000070201046102023000126530ustar 000000000000000.5.5 (2022-10-22) - `Value::as_uint` and `Error::UnexpectedValue` have been added. 0.5.4 (2021-03-26) - WebP format support has been added. 0.5.3 (2021-01-05) - An infinite loop in reading PNG files has been fixed. - PNG reader now handles `std::io::ErrorKind::Interrupted` correctly. 0.5.2 (2020-08-07) - PNG format support has been added. - DCF (Design rule for Camera File system) 2.0 tags have been added. 0.5.1 (2020-02-15) - Exif 2.32 tags have been added. 0.5 (2020-01-26) - Support for HEIF has been added. - `Exif` has been separated from `Reader`. - `Error::description` has been removed because it has been soft-deprecated. 0.4 (2019-12-22) - Support for displaying values with units has been added. - Rust 1.40 or later is required. - The deprecated `tag` module has been removed. - Support for reading up to 8 IFDs has been added. - Enums `Context` and `Error` are now non_exhaustive. - `Value` and `Field` no longer borrows the raw buffer. - Struct `In` has been added to indicate primary/thumbnail images. - `Reader::fields` now returns an iterator. - The associated value of `Value::Undefined` and `Value::Ascii` has been changed from a slice to a `Vec`. 0.3.1 (2018-06-17) - IFDs other than 0th and 1st are ignored for now. 0.3 (2017-10-22) - Enum `Error` now has two new variants: `TooBig` and `NotSupported`. - `Value::Undefined` now has the 2nd member to keep the offset of the value. - Struct `DateTime` now has two new fields: `nanosecond` and `time_offset`. - The tag constants have been changed to associated constants of struct `Tag`. Use `Tag::TagName` instead of `tag::TagName`. 0.2.3 (2017-07-16) - Experimental support for writing Exif data has been added. - The `Hash` trait has been derived for `Tag` and `Context`. - `Reader::get_value` and `Value::iter_uint` have been added. 0.2.2 (2017-06-17) - The `std::fmt::Display` trait has been implemented for `Rational`, `SRational`, and `DateTime`. - The `Copy` and `Clone` traits have been derived for `Rational` and `SRational`. - Converters from `Rational`/`SRational` to `f64`/`f32` have been added. - `Rational::to_f64` and `SRational::to_f64`. - `From` and `From` traits for `f64` and `f32`. - Human readable printing of a `Value` has been supported. The `Value::display_as` method returns an object that implements the `Display` trait. - The `Value::get_uint` method has been added. 0.2.1 (2017-03-27) - A typo in the documentation has been fixed. 0.2 (2017-03-26) - The `Copy` and `Clone` traits have been derived for `Tag`. - The `Tag::default_value` function has been added. - DateTime parser has been added. - A new variant `Error::BlankValue` has been added. - Rust 1.15 is now required to compile. - The `Reader::fields` method now returns a slice instead of a reference to a `Vec`. - The `parse_image` function has been removed. - The `Tag::value` method was renamed to `Tag::number`. 0.1.3 (2017-03-12) - Constants for the new tags in Exif 2.31 have been added. - An ASCII field with zero count 0 is parsed to an empty Vec. - `Tag` and `Context` are no longer re-exported. 0.1.2 (2017-02-25) - Struct `Reader` has been added. - The `parse_image` function has been deperecated. 0.1.1 (2017-01-12) - The `parse_image` function has been added. 0.1 (2016-12-30) - The first public version. kamadak-exif-0.5.5/README000064400000000000000000000025351046102023000130420ustar 00000000000000Exif parsing library written in pure Rust ----------------------------------------- This is a pure-Rust library to parse Exif data. This library parses Exif attributes in a raw Exif data block. It can also read Exif data directly from some image formats. Supported formats are: - TIFF and some RAW image formats based on it - JPEG - HEIF and coding-specific variations including HEIC and AVIF - PNG - WebP Usage ----- Add a dependency entry to your Cargo.toml. Specify "kamadak-exif" if you use crates.io. The canonical name of this crate is "exif", but it is renamed on crates.io to avoid a naming conflict. [dependencies] kamadak-exif = "x.y.z" Add the following to your crate root (before Rust 2018). extern crate exif; Run "cargo doc" in the source directory to generate the API reference. It is also available online at . See examples directory for sample codes. Dependencies ------------ Rust 1.40 or later is required to build. Standards --------- - Exif Version 2.32 - DCF Version 2.0 (Edition 2010) - TIFF Revision 6.0 - ISO/IEC 14496-12:2015 - ISO/IEC 23008-12:2017 - PNG Specification, Version 1.2 - Extensions to the PNG 1.2 Specification, version 1.5.0 - WebP Container Specification, committed on 2018-04-20 kamadak-exif-0.5.5/examples/dumpexif.rs000064400000000000000000000040521046102023000161630ustar 00000000000000// // Copyright (c) 2016 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. // extern crate exif; use std::env; use std::fs::File; use std::io::BufReader; use std::path::{Path, PathBuf}; fn main() { for path in env::args_os().skip(1).map(PathBuf::from) { if let Err(e) = dump_file(&path) { println!("{}: {}", path.display(), e); } } } fn dump_file(path: &Path) -> Result<(), exif::Error> { let file = File::open(path)?; let exif = exif::Reader::new().read_from_container( &mut BufReader::new(&file))?; println!("{}", path.display()); for f in exif.fields() { println!(" {}/{}: {}", f.ifd_num.index(), f.tag, f.display_value().with_unit(&exif)); println!(" {:?}", f.value); } Ok(()) } kamadak-exif-0.5.5/examples/reading.rs000064400000000000000000000065531046102023000157630ustar 00000000000000// // 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. // extern crate exif; use std::fs::File; use std::io::BufReader; use exif::{DateTime, In, Reader, Value, Tag}; fn main() { let file = File::open("tests/exif.jpg").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 // unit, call `with_unit` on the return value of `Field::display_value`. let tag_list = [Tag::ExifVersion, Tag::PixelXDimension, Tag::XResolution, Tag::ImageDescription, Tag::DateTime]; for &tag in tag_list.iter() { if let Some(field) = exif.get_field(tag, In::PRIMARY) { println!("{}: {}", 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) = exif.get_field(Tag::PixelXDimension, In::PRIMARY) { if let Some(width) = field.value.get_uint(0) { println!("Valid width of the image is {}.", width); } } // To convert a Rational or SRational to an f64, `Rational::to_f64` // or `SRational::to_f64` can be used. 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()), _ => {}, } } // To parse a DateTime-like field, `DateTime::from_ascii` can be used. 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]) { println!("Year of DateTime is {}.", datetime.year); } }, _ => {}, } } } kamadak-exif-0.5.5/src/doc.rs000064400000000000000000000145041046102023000140630ustar 00000000000000// // Copyright (c) 2020 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. // //! Documentation // Rust 1.54 stabilized invoking function-like macros in attributes. // We will use it after bumping MSRV. // #![feature(extended_key_value_attributes)], // #[doc = include_str!("../NEWS")] // pub mod news {} macro_rules! doc_module_with_external_source { ($( #[$attr:meta] )* $name: ident, $doc: expr) => { $( #[$attr] )* #[doc = ""] #[doc = $doc] pub mod $name {} } } doc_module_with_external_source!( /// # News news, include_str!("../NEWS")); /// # Upgrade Guide /// /// ## Upgrade from 0.4.x to 0.5.x /// /// ### API compatibilities /// /// * `Reader` has been split into two: `Reader` and `Exif`. /// `Reader` is now the builder for `Exif`, and `Exif` provides /// access to `Field`s via `get_field`, `fields`, and other methods. /// Old code `Reader::new(data)` should be changed to /// `Reader::new().read_raw(data)` or /// `Reader::new().read_from_container(data)`. /// /// The old code using 0.4.x: /// ```ignore /// # use exif::Reader; /// # let file = std::fs::File::open("tests/exif.jpg").unwrap(); /// # let mut bufreader = std::io::BufReader::new(&file); /// let reader = Reader::new(&mut bufreader).unwrap(); /// for f in reader.fields() { /* do something */ } /// ``` /// The new code using 0.5.x: /// ``` /// # use exif::{Exif, Reader}; /// # let file = std::fs::File::open("tests/exif.jpg").unwrap(); /// # let mut bufreader = std::io::BufReader::new(&file); /// let exif = Reader::new().read_from_container(&mut bufreader).unwrap(); /// for f in exif.fields() { /* do something */ } /// ``` /// /// ### Other new features /// /// * Support for parsing Exif in HEIF (HEIC/AVIF) has been added. /// /// ## Upgrade from 0.3.x to 0.4.x /// /// ### API compatibilities /// /// * Use struct `In` instead of `bool` to indicate primary/thumbnail images. /// - On `Reader::get_field`, the old code using 0.3.x: /// ```ignore /// reader.get_field(Tag::DateTime, false) /// ``` /// The new code using 0.4.x: /// ```ignore /// # use exif::{In, Reader, Tag}; /// # let file = std::fs::File::open("tests/exif.tif").unwrap(); /// # let reader = Reader::new( /// # &mut std::io::BufReader::new(&file)).unwrap(); /// reader.get_field(Tag::DateTime, In::PRIMARY) /// # ; /// ``` /// As an additional feature, access to the 2nd or further IFD, /// which some TIFF-based RAW formats may have, is also possible /// with 0.4.x: /// ```ignore /// # use exif::{In, Reader, Tag}; /// # let file = std::fs::File::open("tests/exif.tif").unwrap(); /// # let reader = Reader::new( /// # &mut std::io::BufReader::new(&file)).unwrap(); /// reader.get_field(Tag::ImageWidth, In(2)) /// # ; /// ``` /// - On `Field`, the old code using 0.3.x: /// ```ignore /// if field.thumbnail { /// // for the thumbnail image /// } else { /// // for the primary image /// } /// ``` /// The new code using 0.4.x: /// ``` /// # use exif::{In, Reader}; /// # let file = std::fs::File::open("tests/exif.tif").unwrap(); /// # let exif = Reader::new().read_from_container( /// # &mut std::io::BufReader::new(&file)).unwrap(); /// # let field = exif.fields().next().unwrap(); /// match field.ifd_num { /// In::PRIMARY => {}, // for the primary image /// In::THUMBNAIL => {}, // for the thumbnail image /// _ => {}, /// } /// ``` /// * `Reader::fields` now returns an iterator instead of a slice. /// * Enum variants of `Context` and `Error` are no longer exhaustive. /// You need a wildcard arm (`_`) in a match expression. /// * The associated value of `Value::Undefined` and `Value::Ascii` has /// been changed from a slice to a `Vec`. /// /// ### Other new features /// /// * `Field::display_value` has been introduced. /// It is usually handier than `Value::display_as`. /// ``` /// # let file = std::fs::File::open("tests/exif.tif").unwrap(); /// # let exif = exif::Reader::new().read_from_container( /// # &mut std::io::BufReader::new(&file)).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(); /// # let field = reader.fields().next().unwrap(); /// // Display the value only. /// println!("{}", field.display_value()); /// // Display the value with its unit. If the unit depends on another /// // field, it is taken from the reader. /// println!("{}", field.display_value().with_unit(&reader)); /// ``` /// * `Value` and `Field` are self-contained and no longer borrow the raw /// buffer or `Reader` (thanks to the change of `Value::Undefined` /// and `Value::Ascii` described above). pub mod upgrade {} kamadak-exif-0.5.5/src/endian.rs000064400000000000000000000152741046102023000145610ustar 00000000000000// // Copyright (c) 2016 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. // use std::io; use std::mem; // This is a module to select endianess by using generics // in order to avoid run-time dispatching penalty at the cost of // increased object size. pub trait Endian { fn loadu16(buf: &[u8], from: usize) -> u16; fn loadu32(buf: &[u8], from: usize) -> u32; fn loadu64(buf: &[u8], from: usize) -> u64; fn writeu16(w: &mut W, num: u16) -> io::Result<()> where W: io::Write; fn writeu32(w: &mut W, num: u32) -> io::Result<()> where W: io::Write; fn writeu64(w: &mut W, num: u64) -> io::Result<()> where W: io::Write; } pub struct BigEndian; pub struct LittleEndian; macro_rules! generate_load { ($name:ident, $int_type:ident, $from_func:ident) => ( fn $name(buf: &[u8], offset: usize) -> $int_type { let mut num = [0u8; mem::size_of::<$int_type>()]; num.copy_from_slice( &buf[offset .. offset + mem::size_of::<$int_type>()]); $int_type::$from_func(num) } ) } macro_rules! generate_write { ($name:ident, $int_type:ident, $to_func:ident) => ( fn $name(w: &mut W, num: $int_type) -> io::Result<()> where W: io::Write { let buf = num.$to_func(); w.write_all(&buf) } ) } impl Endian for BigEndian { generate_load!(loadu16, u16, from_be_bytes); generate_load!(loadu32, u32, from_be_bytes); generate_load!(loadu64, u64, from_be_bytes); generate_write!(writeu16, u16, to_be_bytes); generate_write!(writeu32, u32, to_be_bytes); generate_write!(writeu64, u64, to_be_bytes); } impl Endian for LittleEndian { generate_load!(loadu16, u16, from_le_bytes); generate_load!(loadu32, u32, from_le_bytes); generate_load!(loadu64, u64, from_le_bytes); generate_write!(writeu16, u16, to_le_bytes); generate_write!(writeu32, u32, to_le_bytes); generate_write!(writeu64, u64, to_le_bytes); } #[cfg(test)] mod tests { use super::*; #[test] fn loadu16() { assert_eq!(BigEndian::loadu16(&[0x01, 0x02], 0), 0x0102); assert_eq!(BigEndian::loadu16(&[0x01, 0x02, 0x03], 1), 0x0203); assert_eq!(LittleEndian::loadu16(&[0x01, 0x02], 0), 0x0201); assert_eq!(LittleEndian::loadu16(&[0x01, 0x02, 0x03], 1), 0x0302); } #[test] fn loadu32() { assert_eq!(BigEndian::loadu32(&[0x01, 0x02, 0x03, 0x04], 0), 0x01020304); assert_eq!(BigEndian::loadu32(&[0x01, 0x02, 0x03, 0x04, 0x05], 1), 0x02030405); assert_eq!(LittleEndian::loadu32(&[0x01, 0x02, 0x03, 0x04], 0), 0x04030201); assert_eq!(LittleEndian::loadu32(&[0x01, 0x02, 0x03, 0x04, 0x05], 1), 0x05040302); } #[test] fn loadu64() { assert_eq!(BigEndian::loadu64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], 0), 0x0102030405060708); assert_eq!(BigEndian::loadu64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09], 1), 0x0203040506070809); assert_eq!(LittleEndian::loadu64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], 0), 0x0807060504030201); assert_eq!(LittleEndian::loadu64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09], 1), 0x0908070605040302); } #[test] fn writeu16() { let mut buf = Vec::new(); BigEndian::writeu16(&mut buf, 0x0102).unwrap(); LittleEndian::writeu16(&mut buf, 0x0304).unwrap(); assert_eq!(buf, b"\x01\x02\x04\x03"); } #[test] fn writeu32() { let mut buf = Vec::new(); BigEndian::writeu32(&mut buf, 0x01020304).unwrap(); LittleEndian::writeu32(&mut buf, 0x05060708).unwrap(); assert_eq!(buf, b"\x01\x02\x03\x04\x08\x07\x06\x05"); } #[test] fn writeu64() { let mut buf = Vec::new(); BigEndian::writeu64(&mut buf, 0x0102030405060708).unwrap(); LittleEndian::writeu64(&mut buf, 0x090a0b0c0d0e0f10).unwrap(); assert_eq!(buf, b"\x01\x02\x03\x04\x05\x06\x07\x08\ \x10\x0f\x0e\x0d\x0c\x0b\x0a\x09"); } #[test] fn dispatch() { fn dispatch_sub(data: &[u8]) -> u16 where E: Endian { E::loadu16(data, 0) } assert_eq!(dispatch_sub::(&[0x01, 0x02]), 0x0102); assert_eq!(dispatch_sub::(&[0x01, 0x02]), 0x0201); } #[test] fn static_dispatch() { fn dispatch_sub(data: &[u8]) -> u16 where E: Endian { E::loadu16(data, 0) } assert_eq!(dispatch_sub:: as *const (), dispatch_sub:: as *const ()); assert_ne!(dispatch_sub:: as *const (), dispatch_sub:: as *const ()); } #[test] #[should_panic(expected = "index 3 out of range for slice of length 2")] fn out_of_range() { BigEndian::loadu16(&[0x01, 0x02], 1); } // "attempt to add with overflow" with the arithmetic overflow // check, and "slice index starts at 18446744073709551615 but ends // at 1" without it. #[test] #[should_panic(expected = "at")] fn wrap_around() { BigEndian::loadu16(&[0x01, 0x02], (-1isize) as usize); } } kamadak-exif-0.5.5/src/error.rs000064400000000000000000000065431046102023000144530ustar 00000000000000// // Copyright (c) 2016 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. // use std::error; use std::fmt; use std::io; /// An error returned when parsing of Exif data fails. #[derive(Debug)] #[non_exhaustive] pub enum Error { /// Input data was malformed or truncated. InvalidFormat(&'static str), /// Input data could not be read due to an I/O error and /// a `std::io::Error` value is associated with this variant. Io(io::Error), /// Exif attribute information was not found in an image file /// such as JPEG. NotFound(&'static str), /// The value of the field is blank. Some fields have blank values /// whose meanings are defined as "unknown". Such a blank value /// should be treated the same as the absence of the field. BlankValue(&'static str), /// Field values or image data are too big to encode. TooBig(&'static str), /// The field type is not supported and cannnot be encoded. NotSupported(&'static str), /// The field has an unexpected value. UnexpectedValue(&'static str), } impl From for Error { fn from(err: io::Error) -> Error { Error::Io(err) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::InvalidFormat(msg) => f.write_str(msg), Error::Io(ref err) => err.fmt(f), Error::NotFound(ctn) => write!(f, "No Exif data found in {}", ctn), Error::BlankValue(msg) => f.write_str(msg), Error::TooBig(msg) => f.write_str(msg), Error::NotSupported(msg) => f.write_str(msg), Error::UnexpectedValue(msg) => f.write_str(msg), } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { Error::InvalidFormat(_) => None, Error::Io(ref err) => Some(err), Error::NotFound(_) => None, Error::BlankValue(_) => None, Error::TooBig(_) => None, Error::NotSupported(_) => None, Error::UnexpectedValue(_) => None, } } } kamadak-exif-0.5.5/src/isobmff.rs000064400000000000000000000501211046102023000147360ustar 00000000000000// // Copyright (c) 2020 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. // use std::convert::{TryFrom as _, TryInto as _}; use std::io::{BufRead, ErrorKind, Seek, SeekFrom}; use crate::endian::{Endian, BigEndian}; use crate::error::Error; use crate::util::{read64, BufReadExt as _, ReadExt as _}; // Checking "mif1" in the compatible brands should be enough, because // the "heic", "heix", "heim", and "heis" files shall include "mif1" // among the compatible brands [ISO23008-12 B.4.1] [ISO23008-12 B.4.3]. // Same for "msf1" [ISO23008-12 B.4.2] [ISO23008-12 B.4.4]. static HEIF_BRANDS: &[[u8; 4]] = &[*b"mif1", *b"msf1"]; const MAX_EXIF_SIZE: usize = 65535; // Most errors in this file are Error::InvalidFormat. impl From<&'static str> for Error { fn from(err: &'static str) -> Error { Error::InvalidFormat(err) } } pub fn get_exif_attr(reader: &mut R) -> Result, Error> where R: BufRead + Seek { let mut parser = Parser::new(reader); match parser.parse() { Err(Error::Io(ref e)) if e.kind() == ErrorKind::UnexpectedEof => Err("Broken HEIF file".into()), Err(e) => Err(e), Ok(mut buf) => { if buf.len() < 4 { return Err("ExifDataBlock too small".into()); } let offset = BigEndian::loadu32(&buf, 0) as usize; if buf.len() - 4 < offset { return Err("Invalid Exif header offset".into()); } buf.drain(.. 4 + offset); Ok(buf) }, } } #[derive(Debug)] struct Parser { reader: R, // Whether the file type box has been checked. ftyp_checked: bool, // The item where Exif data is stored. item_id: Option, // The location of the item_id. item_location: Option, } #[derive(Debug)] struct Location { construction_method: u8, // index, offset, length extents: Vec<(u64, u64, u64)>, base_offset: u64, } impl Parser where R: BufRead + Seek { fn new(reader: R) -> Self { Self { reader: reader, ftyp_checked: false, item_id: None, item_location: None, } } fn parse(&mut self) -> Result, Error> { while let Some((size, boxtype)) = self.read_box_header()? { match &boxtype { b"ftyp" => { let buf = self.read_file_level_box(size)?; self.parse_ftyp(BoxSplitter::new(&buf))?; self.ftyp_checked = true; }, b"meta" => { if !self.ftyp_checked { return Err("MetaBox found before FileTypeBox".into()); } let buf = self.read_file_level_box(size)?; let exif = self.parse_meta(BoxSplitter::new(&buf))?; return Ok(exif); }, _ => self.skip_file_level_box(size)?, } } Err(Error::NotFound("HEIF")) } // Reads size, type, and largesize, // and returns body size and type. // If no byte can be read due to EOF, None is returned. fn read_box_header(&mut self) -> Result, Error> { if self.reader.is_eof()? { return Ok(None); } let mut buf = [0; 8]; self.reader.read_exact(&mut buf)?; let size = match BigEndian::loadu32(&buf, 0) { 0 => Some(std::u64::MAX), 1 => read64(&mut self.reader)?.checked_sub(16), x => u64::from(x).checked_sub(8), }.ok_or("Invalid box size")?; let boxtype = buf[4..8].try_into().expect("never fails"); Ok(Some((size, boxtype))) } fn read_file_level_box(&mut self, size: u64) -> Result, Error> { let mut buf; match size { std::u64::MAX => { buf = Vec::new(); self.reader.read_to_end(&mut buf)?; }, _ => { let size = size.try_into() .or(Err("Box is larger than the address space"))?; buf = Vec::new(); self.reader.read_exact_len(&mut buf, size)?; }, } Ok(buf) } fn skip_file_level_box(&mut self, size: u64) -> Result<(), Error> { match size { std::u64::MAX => self.reader.seek(SeekFrom::End(0))?, _ => self.reader.seek(SeekFrom::Current( size.try_into().or(Err("Large seek not supported"))?))?, }; Ok(()) } fn parse_ftyp(&mut self, mut boxp: BoxSplitter) -> Result<(), Error> { let head = boxp.slice(8)?; let _major_brand = &head[0..4]; let _minor_version = BigEndian::loadu32(&head, 4); while let Ok(compat_brand) = boxp.array4() { if HEIF_BRANDS.contains(&compat_brand) { return Ok(()); } } Err("No compatible brand recognized in ISO base media file".into()) } fn parse_meta(&mut self, mut boxp: BoxSplitter) -> Result, Error> { let (version, _flags) = boxp.fullbox_header()?; if version != 0 { return Err("Unsupported MetaBox".into()); } let mut idat = None; let mut iloc = None; while !boxp.is_empty() { let (boxtype, mut body) = boxp.child_box()?; match boxtype { b"idat" => idat = Some(body.slice(body.len())?), b"iinf" => self.parse_iinf(body)?, b"iloc" => iloc = Some(body), _ => {}, } } self.item_id.ok_or(Error::NotFound("HEIF"))?; self.parse_iloc(iloc.ok_or("No ItemLocationBox")?)?; let location = self.item_location.as_ref() .ok_or("No matching item in ItemLocationBox")?; let mut buf = Vec::new(); match location.construction_method { 0 => { for &(_, off, len) in &location.extents { let off = location.base_offset.checked_add(off) .ok_or("Invalid offset")?; // Seeking beyond the EOF is allowed and // implementation-defined, but the subsequent read // should fail. self.reader.seek(SeekFrom::Start(off))?; match len { 0 => { self.reader.read_to_end(&mut buf)?; }, _ => { let len = len.try_into() .or(Err("Extent too large"))?; self.reader.read_exact_len(&mut buf, len)?; }, } if buf.len() > MAX_EXIF_SIZE { return Err("Exif data too large".into()); } } }, 1 => { let idat = idat.ok_or("No ItemDataBox")?; for &(_, off, len) in &location.extents { let off = location.base_offset.checked_add(off) .ok_or("Invalid offset")?; let end = off.checked_add(len).ok_or("Invalid length")?; let off = off.try_into().or(Err("Offset too large"))?; let end = end.try_into().or(Err("Length too large"))?; buf.extend_from_slice(match len { 0 => idat.get(off..), _ => idat.get(off..end), }.ok_or("Out of ItemDataBox")?); if buf.len() > MAX_EXIF_SIZE { return Err("Exif data too large".into()); } } }, 2 => return Err(Error::NotSupported( "Construction by item offset is not supported")), _ => return Err("Invalid construction_method".into()), } Ok(buf) } fn parse_iloc(&mut self, mut boxp: BoxSplitter) -> Result<(), Error> { let (version, _flags) = boxp.fullbox_header()?; let tmp = boxp.uint16().map(usize::from)?; let (offset_size, length_size, base_offset_size) = (tmp >> 12, tmp >> 8 & 0xf, tmp >> 4 & 0xf); let index_size = match version { 1 | 2 => tmp & 0xf, _ => 0 }; let item_count = match version { 0 | 1 => boxp.uint16()?.into(), 2 => boxp.uint32()?, _ => return Err("Unsupported ItemLocationBox".into()), }; for _ in 0..item_count { let item_id = match version { 0 | 1 => boxp.uint16()?.into(), 2 => boxp.uint32()?, _ => unreachable!(), }; let construction_method = match version { 0 => 0, 1 | 2 => boxp.slice(2).map(|x| x[1] & 0xf)?, _ => unreachable!(), }; let data_ref_index = boxp.uint16()?; if construction_method == 0 && data_ref_index != 0 { return Err(Error::NotSupported( "External data reference is not supported")); } let base_offset = boxp.size048(base_offset_size)? .ok_or("Invalid base_offset_size")?; let extent_count = boxp.uint16()?.into(); if self.item_id == Some(item_id) { let mut extents = Vec::with_capacity(extent_count); for _ in 0..extent_count { let index = boxp.size048(index_size)? .ok_or("Invalid index_size")?; let offset = boxp.size048(offset_size)? .ok_or("Invalid offset_size")?; let length = boxp.size048(length_size)? .ok_or("Invalid length_size")?; extents.push((index, offset, length)); } self.item_location = Some(Location { construction_method, extents, base_offset }); } else { // (15 + 15 + 15) * u16::MAX never overflows. boxp.slice((index_size + offset_size + length_size) * extent_count)?; } } Ok(()) } fn parse_iinf(&mut self, mut boxp: BoxSplitter) -> Result<(), Error> { let (version, _flags) = boxp.fullbox_header()?; let entry_count = match version { 0 => boxp.uint16()?.into(), _ => boxp.uint32()?, }; for _ in 0..entry_count { let (boxtype, body) = boxp.child_box()?; match boxtype { b"infe" => self.parse_infe(body)?, _ => {}, } } Ok(()) } fn parse_infe(&mut self, mut boxp: BoxSplitter) -> Result<(), Error> { let (version, _flags) = boxp.fullbox_header()?; let item_id = match version { 2 => boxp.uint16()?.into(), 3 => boxp.uint32()?, _ => return Err("Unsupported ItemInfoEntry".into()), }; let _item_protection_index = boxp.slice(2)?; let item_type = boxp.slice(4)?; if item_type == b"Exif" { self.item_id = Some(item_id); } Ok(()) } } pub fn is_heif(buf: &[u8]) -> bool { let mut boxp = BoxSplitter::new(buf); while let Ok((boxtype, mut body)) = boxp.child_box() { if boxtype == b"ftyp" { let _major_brand_minor_version = if body.slice(8).is_err() { return false; }; while let Ok(compat_brand) = body.array4() { if HEIF_BRANDS.contains(&compat_brand) { return true; } } return false; } } false } struct BoxSplitter<'a> { inner: &'a [u8], } impl<'a> BoxSplitter<'a> { fn new(slice: &'a [u8]) -> BoxSplitter<'a> { Self { inner: slice } } fn is_empty(&self) -> bool { self.inner.is_empty() } fn len(&self) -> usize { self.inner.len() } // Returns type and body. fn child_box(&mut self) -> Result<(&'a [u8], BoxSplitter<'a>), Error> { let size = self.uint32()? as usize; let boxtype = self.slice(4)?; let body_len = match size { 0 => Some(self.len()), 1 => usize::try_from(self.uint64()?) .or(Err("Box is larger than the address space"))? .checked_sub(16), _ => size.checked_sub(8), }.ok_or("Invalid box size")?; let body = self.slice(body_len)?; Ok((boxtype, BoxSplitter::new(body))) } // Returns 0-, 4-, or 8-byte unsigned integer. fn size048(&mut self, size: usize) -> Result, Error> { match size { 0 => Ok(Some(0)), 4 => self.uint32().map(u64::from).map(Some), 8 => self.uint64().map(Some), _ => Ok(None), } } // Returns version and flags. fn fullbox_header(&mut self) -> Result<(u32, u32), Error> { let tmp = self.uint32()?; Ok((tmp >> 24, tmp & 0xffffff)) } fn uint16(&mut self) -> Result { self.slice(2).map(|num| BigEndian::loadu16(num, 0)) } fn uint32(&mut self) -> Result { self.slice(4).map(|num| BigEndian::loadu32(num, 0)) } fn uint64(&mut self) -> Result { self.slice(8).map(|num| BigEndian::loadu64(num, 0)) } fn array4(&mut self) -> Result<[u8; 4], Error> { self.slice(4).map(|x| x.try_into().expect("never fails")) } fn slice(&mut self, at: usize) -> Result<&'a [u8], Error> { let slice = self.inner.get(..at).ok_or("Box too small")?; self.inner = &self.inner[at..]; Ok(slice) } } #[cfg(test)] mod tests { use std::io::Cursor; use super::*; #[test] fn extract() { let file = std::fs::File::open("tests/exif.heic").unwrap(); let buf = get_exif_attr( &mut std::io::BufReader::new(&file)).unwrap(); assert_eq!(buf.len(), 79); assert!(buf.starts_with(b"MM\x00\x2a")); assert!(buf.ends_with(b"xif\0")); } #[test] fn unknown_before_ftyp() { let data = b"\0\0\0\x09XXXXx\ \0\0\0\x14ftypmif1\0\0\0\0mif1\ \0\0\0\x57meta\0\0\0\0\ \0\0\0\x18iloc\x01\0\0\0\0\0\0\x01\x1e\x1d\0\x01\0\0\0\x01\ \0\0\0\x22iinf\0\0\0\0\0\x01\ \0\0\0\x14infe\x02\0\0\0\x1e\x1d\0\0Exif\ \0\0\0\x11idat\0\0\0\x01xabcd"; assert!(is_heif(data)); let exif = get_exif_attr(&mut Cursor::new(&data[..])).unwrap(); assert_eq!(exif, b"abcd"); } #[test] fn bad_exif_data_block() { let data = b"\0\0\0\x14ftypmif1\0\0\0\0mif1\ \0\0\0\x52meta\0\0\0\0\ \0\0\0\x18iloc\x01\0\0\0\0\0\0\x01\x1e\x1d\0\x01\0\0\0\x01\ \0\0\0\x22iinf\0\0\0\0\0\x01\ \0\0\0\x14infe\x02\0\0\0\x1e\x1d\0\0Exif\ \0\0\0\x0cidat\0\0\0\x01"; assert_err_pat!(get_exif_attr(&mut Cursor::new(&data[..])), Error::InvalidFormat("Invalid Exif header offset")); let data = b"\0\0\0\x14ftypmif1\0\0\0\0mif1\ \0\0\0\x51meta\0\0\0\0\ \0\0\0\x18iloc\x01\0\0\0\0\0\0\x01\x1e\x1d\0\x01\0\0\0\x01\ \0\0\0\x22iinf\0\0\0\0\0\x01\ \0\0\0\x14infe\x02\0\0\0\x1e\x1d\0\0Exif\ \0\0\0\x0bidat\0\0\0"; assert_err_pat!(get_exif_attr(&mut Cursor::new(&data[..])), Error::InvalidFormat("ExifDataBlock too small")); } #[test] fn parser_box_header() { // size let mut p = Parser::new(Cursor::new(b"\0\0\0\x08abcd")); assert_eq!(p.read_box_header().unwrap(), Some((0, *b"abcd"))); let mut p = Parser::new(Cursor::new(b"\0\0\0\x08abc")); assert_err_pat!(p.read_box_header(), Error::Io(_)); let mut p = Parser::new(Cursor::new(b"\0\0\0\x07abcd")); assert_err_pat!(p.read_box_header(), Error::InvalidFormat(_)); // max size let mut p = Parser::new(Cursor::new(b"\xff\xff\xff\xffabcd")); assert_eq!(p.read_box_header().unwrap(), Some((0xffffffff - 8, *b"abcd"))); // to the end of the file let mut p = Parser::new(Cursor::new(b"\0\0\0\0abcd")); assert_eq!(p.read_box_header().unwrap(), Some((std::u64::MAX, *b"abcd"))); // largesize let mut p = Parser::new(Cursor::new( b"\0\0\0\x01abcd\0\0\0\0\0\0\0\x10")); assert_eq!(p.read_box_header().unwrap(), Some((0, *b"abcd"))); let mut p = Parser::new(Cursor::new( b"\0\0\0\x01abcd\0\0\0\0\0\0\0")); assert_err_pat!(p.read_box_header(), Error::Io(_)); let mut p = Parser::new(Cursor::new( b"\0\0\0\x01abcd\0\0\0\0\0\0\0\x0f")); assert_err_pat!(p.read_box_header(), Error::InvalidFormat(_)); // max largesize let mut p = Parser::new(Cursor::new( b"\0\0\0\x01abcd\xff\xff\xff\xff\xff\xff\xff\xff")); assert_eq!(p.read_box_header().unwrap(), Some((std::u64::MAX.wrapping_sub(16), *b"abcd"))); } #[test] fn is_heif_test() { // HEIF (with any coding format) assert!(is_heif(b"\0\0\0\x14ftypmif1\0\0\0\0mif1")); // HEIC assert!(is_heif(b"\0\0\0\x18ftypheic\0\0\0\0heicmif1")); // HEIC image sequence assert!(is_heif(b"\0\0\0\x18ftyphevc\0\0\0\0msf1hevc")); // unknown major brand but compatible with HEIF assert!(is_heif(b"\0\0\0\x18ftypXXXX\0\0\0\0XXXXmif1")); // incomplete brand (OK to ignore?) assert!(is_heif(b"\0\0\0\x15ftypmif1\0\0\0\0mif1h")); assert!(is_heif(b"\0\0\0\x16ftypmif1\0\0\0\0mif1he")); assert!(is_heif(b"\0\0\0\x17ftypmif1\0\0\0\0mif1hei")); // ISO base media file but not a HEIF assert!(!is_heif(b"\0\0\0\x14ftypmp41\0\0\0\0mp41")); // missing compatible brands (what should we do?) assert!(!is_heif(b"\0\0\0\x10ftypmif1\0\0\0\0")); // truncated box let mut data: &[u8] = b"\0\0\0\x14ftypmif1\0\0\0\0mif1"; while let Some((_, rest)) = data.split_last() { data = rest; assert!(!is_heif(data)); } // short box size assert!(!is_heif(b"\0\0\0\x13ftypmif1\0\0\0\0mif1")); } #[test] fn box_splitter() { let buf = b"0123456789abcdef"; let mut boxp = BoxSplitter::new(buf); assert_err_pat!(boxp.slice(17), Error::InvalidFormat(_)); assert_eq!(boxp.slice(16).unwrap(), buf); assert_err_pat!(boxp.slice(std::usize::MAX), Error::InvalidFormat(_)); let mut boxp = BoxSplitter::new(buf); assert_eq!(boxp.slice(1).unwrap(), b"0"); assert_eq!(boxp.uint16().unwrap(), 0x3132); assert_eq!(boxp.uint32().unwrap(), 0x33343536); assert_eq!(boxp.uint64().unwrap(), 0x3738396162636465); } } kamadak-exif-0.5.5/src/jpeg.rs000064400000000000000000000124531046102023000142440ustar 00000000000000// // Copyright (c) 2016 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. // use std::io::{BufRead, ErrorKind}; use crate::error::Error; use crate::util::{read8, read16}; mod marker { // The first byte of a marker. pub const P: u8 = 0xff; // Marker codes. pub const Z: u8 = 0x00; // Not a marker but a byte stuffing. pub const TEM: u8 = 0x01; pub const RST0: u8 = 0xd0; pub const RST7: u8 = 0xd7; pub const SOI: u8 = 0xd8; pub const EOI: u8 = 0xd9; pub const SOS: u8 = 0xda; pub const APP1: u8 = 0xe1; } // SOI marker as the JPEG header. const JPEG_SIG: [u8; 2] = [marker::P, marker::SOI]; // Exif identifier code "Exif\0\0". [EXIF23 4.7.2] const EXIF_ID: [u8; 6] = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00]; /// Get the Exif attribute information segment from a JPEG file. pub fn get_exif_attr(reader: &mut R) -> Result, Error> where R: BufRead { match get_exif_attr_sub(reader) { Err(Error::Io(ref e)) if e.kind() == ErrorKind::UnexpectedEof => Err(Error::InvalidFormat("Broken JPEG file")), r => r, } } fn get_exif_attr_sub(reader: &mut R) -> Result, Error> where R: BufRead { let mut soi = [0u8; 2]; reader.read_exact(&mut soi)?; if soi != [marker::P, marker::SOI] { return Err(Error::InvalidFormat("Not a JPEG file")); } loop { // Find a marker prefix. Discard non-ff bytes, which appear if // we are in the scan data after SOS or we are out of sync. reader.read_until(marker::P, &mut Vec::new())?; // Get a marker code. let mut code; loop { code = read8(reader)?; if code != marker::P { break; } } // Continue or return early on stand-alone markers. match code { marker::Z | marker::TEM | marker::RST0..=marker::RST7 => continue, marker::SOI => return Err(Error::InvalidFormat("Unexpected SOI")), marker::EOI => return Err(Error::NotFound("JPEG")), _ => {}, } // Read marker segments. let len = read16(reader)?.checked_sub(2) .ok_or(Error::InvalidFormat("Invalid segment length"))?; let mut seg = vec![0; len.into()]; reader.read_exact(&mut seg)?; if code == marker::APP1 && seg.starts_with(&EXIF_ID) { seg.drain(..EXIF_ID.len()); return Ok(seg); } if code == marker::SOS { // Skipping the scan data is handled in the main loop, // so there is nothing to do here. } } } pub fn is_jpeg(buf: &[u8]) -> bool { buf.starts_with(&JPEG_SIG) } #[cfg(test)] mod tests { use super::*; #[test] fn truncated() { let sets: &[&[u8]] = &[ b"", b"\xff", b"\xff\xd8", b"\xff\xd8\x00", b"\xff\xd8\xff", b"\xff\xd8\xff\xe1\x00\x08\x03\x04", ]; for &data in sets { assert_err_pat!(get_exif_attr(&mut &data[..]), Error::InvalidFormat("Broken JPEG file")); } let mut data = b"\xff\xd8\xff\xe1\x00\x08Exif\0\0".to_vec(); assert_eq!(get_exif_attr(&mut &data[..]).unwrap(), b""); while let Some(_) = data.pop() { get_exif_attr(&mut &data[..]).unwrap_err(); } } #[test] fn no_exif() { let data = b"\xff\xd8\xff\xd9"; assert_err_pat!(get_exif_attr(&mut &data[..]), Error::NotFound(_)); } #[test] fn out_of_sync() { let data = b"\xff\xd8\x01\x02\x03\xff\x00\xff\xd9"; assert_err_pat!(get_exif_attr(&mut &data[..]), Error::NotFound(_)); } #[test] fn empty() { let data = b"\xff\xd8\xff\xe1\x00\x08Exif\0\0\xff\xd9"; assert_ok!(get_exif_attr(&mut &data[..]), []); } #[test] fn non_empty() { let data = b"\xff\xd8\xff\xe1\x00\x0aExif\0\0\xbe\xad\xff\xd9"; assert_ok!(get_exif_attr(&mut &data[..]), [0xbe, 0xad]); } } kamadak-exif-0.5.5/src/lib.rs000064400000000000000000000106051046102023000140620ustar 00000000000000// // Copyright (c) 2016 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. // //! This is a pure-Rust library to parse Exif data. //! //! This library parses Exif attributes in a raw Exif data block. //! It can also read Exif data directly from some image formats //! including TIFF, JPEG, HEIF, PNG, and WebP. //! //! # Examples //! //! To parse Exif attributes in an image file, //! use `Reader::read_from_container`. //! To convert a field value to a string, use `Field::display_value`. //! //! ``` //! # fn main() -> Result<(), Box> { //! for path in &["tests/exif.jpg", "tests/exif.tif"] { //! let file = std::fs::File::open(path)?; //! let mut bufreader = std::io::BufReader::new(&file); //! let exifreader = exif::Reader::new(); //! let exif = exifreader.read_from_container(&mut bufreader)?; //! for f in exif.fields() { //! println!("{} {} {}", //! f.tag, f.ifd_num, f.display_value().with_unit(&exif)); //! } //! } //! # Ok(()) } //! ``` //! //! To process a field value programmatically in your application, //! use the value itself (associated value of enum `Value`) //! rather than a stringified one. //! //! ``` //! # use exif::{In, Reader, Tag, Value}; //! # let file = std::fs::File::open("tests/exif.tif").unwrap(); //! # let exif = Reader::new().read_from_container( //! # &mut std::io::BufReader::new(&file)).unwrap(); //! # macro_rules! eprintln { ($($tt:tt)*) => (panic!($($tt)*)) } //! // Orientation is stored as a SHORT. You could match `orientation.value` //! // against `Value::Short`, but the standard recommends that readers //! // should accept BYTE, SHORT, or LONG values for any unsigned integer //! // field. `Value::get_uint` is provided for that purpose. //! match exif.get_field(Tag::Orientation, In::PRIMARY) { //! Some(orientation) => //! match orientation.value.get_uint(0) { //! Some(v @ 1..=8) => println!("Orientation {}", v), //! _ => eprintln!("Orientation value is broken"), //! }, //! None => eprintln!("Orientation tag is missing"), //! } //! // XResolution is stored as a RATIONAL. //! match exif.get_field(Tag::XResolution, In::PRIMARY) { //! Some(xres) => //! match xres.value { //! Value::Rational(ref v) if !v.is_empty() => //! println!("XResolution {}", v[0]), //! _ => eprintln!("XResolution value is broken"), //! }, //! None => eprintln!("XResolution tag is missing"), //! } //! ``` //! //! # Upgrade Guide //! //! See the [upgrade guide](doc/upgrade/index.html) for API incompatibilities. pub use error::Error; pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg; pub use reader::{Exif, Reader}; pub use tag::{Context, Tag}; pub use tiff::{DateTime, Field, In}; pub use tiff::parse_exif; pub use value::Value; pub use value::{Rational, SRational}; /// The interfaces in this module are experimental and unstable. pub mod experimental { pub use crate::writer::Writer; } #[cfg(test)] #[macro_use] mod tmacro; pub mod doc; mod endian; mod error; mod isobmff; mod jpeg; mod png; mod reader; mod tag; mod tiff; mod util; mod value; mod webp; mod writer; kamadak-exif-0.5.5/src/png.rs000064400000000000000000000102761046102023000141040ustar 00000000000000// // Copyright (c) 2020 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. // use std::io::{BufRead, ErrorKind}; use crate::endian::{Endian, BigEndian}; use crate::error::Error; use crate::util::{BufReadExt as _, ReadExt as _}; // PNG file signature [PNG12 12.12]. const PNG_SIG: [u8; 8] = *b"\x89PNG\x0d\x0a\x1a\x0a"; // The four-byte chunk type for Exif data. const EXIF_CHUNK_TYPE: [u8; 4] = *b"eXIf"; // Get the contents of the eXIf chunk from a PNG file. pub fn get_exif_attr(reader: &mut R) -> Result, Error> where R: BufRead { match get_exif_attr_sub(reader) { Err(Error::Io(ref e)) if e.kind() == ErrorKind::UnexpectedEof => Err(Error::InvalidFormat("Broken PNG file")), r => r, } } // The location of the eXIf chunk is restricted [PNGEXT150 3.7], but this // reader is liberal about it. fn get_exif_attr_sub(reader: &mut R) -> Result, Error> where R: BufRead { let mut sig = [0u8; 8]; reader.read_exact(&mut sig)?; if sig != PNG_SIG { return Err(Error::InvalidFormat("Not a PNG file")); } // Scan the series of chunks. loop { if reader.is_eof()? { return Err(Error::NotFound("PNG")); } let mut lenbuf = [0; 4]; reader.read_exact(&mut lenbuf)?; let len = BigEndian::loadu32(&lenbuf, 0) as usize; let mut ctype = [0u8; 4]; reader.read_exact(&mut ctype)?; if ctype == EXIF_CHUNK_TYPE { let mut data = Vec::new(); reader.read_exact_len(&mut data, len)?; return Ok(data); } // Chunk data and CRC. reader.discard_exact(len.checked_add(4).ok_or( Error::InvalidFormat("Invalid chunk length"))?)?; } } pub fn is_png(buf: &[u8]) -> bool { buf.starts_with(&PNG_SIG) } #[cfg(test)] mod tests { use super::*; #[test] fn truncated() { let sets: &[&[u8]] = &[ b"", b"\x89", b"\x89PNG\x0d\x0a\x1a", ]; for &data in sets { assert_err_pat!(get_exif_attr(&mut &data[..]), Error::InvalidFormat("Broken PNG file")); } let mut data = b"\x89PNG\x0d\x0a\x1a\x0a\0\0\0\x04eXIfExif".to_vec(); assert_eq!(get_exif_attr(&mut &data[..]).unwrap(), b"Exif"); while let Some(_) = data.pop() { get_exif_attr(&mut &data[..]).unwrap_err(); } } #[test] fn no_exif() { let data = b"\x89PNG\x0d\x0a\x1a\x0a"; assert_err_pat!(get_exif_attr(&mut &data[..]), Error::NotFound(_)); } #[test] fn empty() { let data = b"\x89PNG\x0d\x0a\x1a\x0a\0\0\0\0eXIfCRC_"; assert_ok!(get_exif_attr(&mut &data[..]), []); } #[test] fn non_empty() { let data = b"\x89PNG\x0d\x0a\x1a\x0a\0\0\0\x02eXIf\xbe\xadCRC_"; assert_ok!(get_exif_attr(&mut &data[..]), [0xbe, 0xad]); } } kamadak-exif-0.5.5/src/reader.rs000064400000000000000000000265611046102023000145660ustar 00000000000000// // 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. // use std::collections::HashMap; use std::io; use std::io::Read; use crate::error::Error; use crate::isobmff; use crate::jpeg; use crate::png; use crate::tag::Tag; use crate::tiff; use crate::tiff::{Field, IfdEntry, In, ProvideUnit}; use crate::webp; /// A struct to parse the Exif attributes and /// create an `Exif` instance that holds the results. /// /// # Examples /// ``` /// # 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> { /// use exif::{In, Reader, Tag}; /// 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"))?; /// assert_eq!(xres.display_value().with_unit(&exif).to_string(), /// "72 pixels per inch"); /// # Ok(()) } /// ``` pub struct Reader { } impl Reader { /// Constructs 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 mut parser = tiff::Parser::new(); parser.parse(&data)?; let entry_map = parser.entries.iter().enumerate() .map(|(i, e)| (e.ifd_num_tag(), i)).collect(); Ok(Exif { buf: data, entries: parser.entries, entry_map: entry_map, little_endian: parser.little_endian, }) } /// Reads an image file and parses the Exif attributes in it. /// If an error occurred, `exif::Error` is returned. /// /// Supported formats are: /// - TIFF and some RAW image formats based on it /// - JPEG /// - HEIF and coding-specific variations including HEIC and AVIF /// - PNG /// - WebP /// /// 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(&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)?; 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))?; } else if png::is_png(&buf) { buf = png::get_exif_attr(&mut buf.chain(reader))?; } else if isobmff::is_heif(&buf) { reader.seek(io::SeekFrom::Start(0))?; buf = isobmff::get_exif_attr(reader)?; } else if webp::is_webp(&buf) { buf = webp::get_exif_attr(&mut buf.chain(reader))?; } else { return Err(Error::InvalidFormat("Unknown image format")); } self.read_raw(buf) } } /// A struct that holds the parsed Exif attributes. /// /// # Examples /// ``` /// # fn main() { sub(); } /// # fn sub() -> Option<()> { /// # 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)?; /// 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()); /// } /// # Some(()) } /// ``` 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] { &self.buf[..] } /// Returns an iterator of Exif fields. #[inline] pub fn fields(&self) -> impl ExactSizeIterator { self.entries.iter() .map(move |e| e.ref_field(&self.buf, self.little_endian)) } /// Returns true if the Exif data (TIFF structure) is in the /// little-endian byte order. #[inline] pub fn little_endian(&self) -> bool { self.little_endian } /// Returns a reference to the Exif field specified by the tag /// and the IFD number. #[inline] pub fn get_field(&self, tag: Tag, ifd_num: In) -> Option<&Field> { self.entry_map.get(&(ifd_num, tag)) .map(|&i| self.entries[i].ref_field(&self.buf, self.little_endian)) } } 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) } } #[cfg(test)] mod tests { use std::fs::File; use std::io::BufReader; use crate::tag::Context; use crate::value::Value; use super::*; #[test] fn get_field() { let file = File::open("tests/yaminabe.tif").unwrap(); let exif = Reader::new().read_from_container( &mut BufReader::new(&file)).unwrap(); match exif.get_field(Tag::ImageDescription, In(0)).unwrap().value { Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test image"]), ref v => panic!("wrong variant {:?}", v) } 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 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) } } #[test] fn display_value_with_unit() { let file = File::open("tests/yaminabe.tif").unwrap(); let exif = Reader::new().read_from_container( &mut BufReader::new(&file)).unwrap(); // No unit. 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 = 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 = 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 = 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 = 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 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], std::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], std::f64::MIN), _ => panic!(), } } } #[test] fn heif() { let file = std::fs::File::open("tests/exif.heic").unwrap(); let exif = Reader::new().read_from_container( &mut std::io::BufReader::new(&file)).unwrap(); 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"); } #[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"); } #[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\""); } } kamadak-exif-0.5.5/src/tag.rs000064400000000000000000001543721046102023000141010ustar 00000000000000// // Copyright (c) 2016 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. // use std::fmt; use crate::error::Error; use crate::value; use crate::value::Value; use crate::util::atou16; /// A tag of a TIFF/Exif field. /// /// Some well-known tags are provided as associated constants of /// this type. The constant names follow the Exif specification /// but not the Rust naming conventions. /// /// A non-predefined tag can also be specified /// by the context and the number as in `Tag(Context::Tiff, 0x100)`. // // This is not an enum to keep safety and API stability, while // supporting unknown tag numbers. This comment is based on the // behavior of Rust 1.12. // Storing unknown values in a repr(u16) enum is unsafe. The compiler // assumes that there is no undefined discriminant even with a C-like // enum, so the exhaustiveness check of a match expression will break. // Storing unknown values in a special variant such as Unknown(u16) // tends to break backward compatibility. When Tag::VariantFoo is // defined in a new version of the library, the old codes using // Tag::Unknown(Foo's tag number) will break. // // Use of constants is restricted in patterns. As of Rust 1.12, // PartialEq and Eq need to be _automatically derived_ for Tag to // emulate structural equivalency. // #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Tag(pub Context, pub u16); impl Tag { /// Returns the context of the tag. /// /// # Examples /// ``` /// use exif::{Context, Tag}; /// assert_eq!(Tag::XResolution.context(), Context::Tiff); /// assert_eq!(Tag::DateTimeOriginal.context(), Context::Exif); /// ``` #[inline] pub fn context(self) -> Context { self.0 } /// Returns the tag number. /// /// # Examples /// ``` /// use exif::Tag; /// assert_eq!(Tag::XResolution.number(), 0x11a); /// assert_eq!(Tag::DateTimeOriginal.number(), 0x9003); /// ``` #[inline] pub fn number(self) -> u16 { self.1 } /// Returns the description of the tag. #[inline] pub fn description(&self) -> Option<&str> { get_tag_info(*self).map(|ti| ti.desc) } /// Returns the default value of the tag. `None` is returned if /// it is not defined in the standard or it depends on another tag. #[inline] pub fn default_value(&self) -> Option { get_tag_info(*self).and_then(|ti| (&ti.default).into()) } pub(crate) fn unit(self) -> Option<&'static [UnitPiece]> { get_tag_info(self).and_then(|ti| ti.unit) } } impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match get_tag_info(*self) { Some(ti) => f.pad(ti.name), None => f.pad(&format!("{:?}", self)), } } } /// An enum that indicates how a tag number is interpreted. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[non_exhaustive] pub enum Context { /// TIFF attributes defined in the TIFF Rev. 6.0 specification. Tiff, // 0th/1st IFD (toplevel) /// Exif attributes. Exif, // -- Exif IFD /// GPS attributes. Gps, // -- GPS IFD /// Interoperability attributes. Interop, // -- Exif IFD -- Interoperability IFD } #[derive(Debug)] pub enum UnitPiece { Value, Str(&'static str), Tag(Tag), } macro_rules! unit { () => ( None ); ( $str:literal ) => ( unit![V, concat!(" ", $str)] ); ( Tag::$tag:ident ) => ( unit![V, " ", Tag::$tag] ); ( $($tokens:tt)* ) => ( Some(unit_expand!( ; $($tokens)* , )) ); } macro_rules! unit_expand { ( $($built:expr),* ; ) => ( &[$($built),*] ); ( $($built:expr),* ; , ) => ( &[$($built),*] ); ( $($built:expr),* ; V, $($rest:tt)* ) => ( unit_expand!($($built,)* UnitPiece::Value ; $($rest)*) ); ( $($built:expr),* ; $str:literal, $($rest:tt)* ) => ( unit_expand!($($built,)* UnitPiece::Str($str) ; $($rest)*) ); ( $($built:expr),* ; concat!($($strs:literal),*), $($rest:tt)* ) => ( unit_expand!($($built,)* UnitPiece::Str(concat!($($strs),*)) ; $($rest)*) ); ( $($built:expr),* ; Tag::$tag:ident, $($rest:tt)* ) => ( unit_expand!($($built,)* UnitPiece::Tag(Tag::$tag) ; $($rest)*) ); } macro_rules! generate_well_known_tag_constants { ( $( |$ctx:path| $( // Copy the doc attribute to the actual definition. $( #[$attr:meta] )* ($name:ident, $num:expr, $defval:expr, $dispval:ident, $unit:expr, $desc:expr) ),+, )+ ) => ( // This is not relevant for associated constants, because // they cannot be imported even with "uniform paths". // /// It is not recommended to import the constants directly into // /// your namespace; import the module and use with the module name // /// like `tag::DateTime`. The constant names follow the Exif // /// specification but not the Rust naming conventions, and a user // /// of the constants will get the non_upper_case_globals warning // /// if a bare constant is used in a match arm. // // This is discussed in // // . impl Tag { $($( $( #[$attr] )* #[allow(non_upper_case_globals)] pub const $name: Tag = Tag($ctx, $num); )+)+ } mod tag_info { use std::fmt; use crate::value::Value; use crate::value::DefaultValue; use super::{Tag, UnitPiece}; pub struct TagInfo { pub name: &'static str, pub desc: &'static str, pub default: DefaultValue, pub dispval: fn(&mut dyn fmt::Write, &Value) -> fmt::Result, pub unit: Option<&'static [UnitPiece]>, } $($( #[allow(non_upper_case_globals)] pub static $name: TagInfo = TagInfo { name: stringify!($name), desc: $desc, default: $defval, dispval: super::$dispval, unit: $unit, }; )+)+ } fn get_tag_info(tag: Tag) -> Option<&'static tag_info::TagInfo> { match tag { $($( Tag::$name => Some(&tag_info::$name), )+)+ _ => None, } } ) } // Tag constant names do not follow the Rust naming conventions but // the Exif field names: camel cases and all-capital acronyms. generate_well_known_tag_constants!( // Exif-specific IFDs [EXIF23 4.6.3]. |Context::Tiff| /// A pointer to the Exif IFD. This is used for the internal structure /// of Exif data and will not be returned to the user. #[doc(hidden)] (ExifIFDPointer, 0x8769, DefaultValue::None, d_default, unit![], "Exif IFD pointer"), /// A pointer to the GPS IFD. This is used for the internal structure /// of Exif data and will not be returned to the user. #[doc(hidden)] (GPSInfoIFDPointer, 0x8825, DefaultValue::None, d_default, unit![], "GPS Info IFD pointer"), |Context::Exif| /// A pointer to the interoperability IFD. This is used for the internal /// structure of Exif data and will not be returned to the user. #[doc(hidden)] (InteropIFDPointer, 0xa005, DefaultValue::None, d_default, unit![], "Interoperability IFD pointer"), // TIFF primary and thumbnail attributes [EXIF23 4.6.4 Table 4, // 4.6.8 Table 17, and 4.6.8 Table 21]. |Context::Tiff| (ImageWidth, 0x100, DefaultValue::None, d_default, unit!["pixels"], "Image width"), (ImageLength, 0x101, DefaultValue::None, d_default, unit!["pixels"], "Image height"), (BitsPerSample, 0x102, DefaultValue::Short(&[8, 8, 8]), d_default, unit![], "Number of bits per component"), (Compression, 0x103, DefaultValue::None, d_compression, unit![], "Compression scheme"), (PhotometricInterpretation, 0x106, DefaultValue::None, d_photointp, unit![], "Pixel composition"), (ImageDescription, 0x10e, DefaultValue::None, d_default, unit![], "Image title"), /// Manufacturer of the image input equipment. (Make, 0x10f, DefaultValue::None, d_default, unit![], "Manufacturer of image input equipment"), /// Model name or number of the image input equipment. (Model, 0x110, DefaultValue::None, d_default, unit![], "Model of image input equipment"), (StripOffsets, 0x111, DefaultValue::None, d_default, unit![], "Image data location"), (Orientation, 0x112, DefaultValue::Short(&[1]), d_orientation, unit![], "Orientation of image"), (SamplesPerPixel, 0x115, DefaultValue::Short(&[3]), d_default, unit![], "Number of components"), (RowsPerStrip, 0x116, DefaultValue::None, d_default, unit![], "Number of rows per strip"), (StripByteCounts, 0x117, DefaultValue::None, d_default, unit![], "Bytes per compressed strip"), (XResolution, 0x11a, DefaultValue::Rational(&[(72, 1)]), d_decimal, unit![V, " pixels per ", Tag::ResolutionUnit], "Image resolution in width direction"), (YResolution, 0x11b, DefaultValue::Rational(&[(72, 1)]), d_decimal, unit![V, " pixels per ", Tag::ResolutionUnit], "Image resolution in height direction"), (PlanarConfiguration, 0x11c, DefaultValue::Short(&[1]), d_planarcfg, unit![], "Image data arrangement"), /// Unit of XResolution and YResolution fields. (ResolutionUnit, 0x128, DefaultValue::Short(&[2]), d_resunit, unit![], "Unit of X and Y resolution"), (TransferFunction, 0x12d, DefaultValue::None, d_default, unit![], "Transfer function"), /// Name and version of the software used to create the image. (Software, 0x131, DefaultValue::None, d_default, unit![], "Software used"), /// Date and time when the image file was created or last edited. /// For the time when the picture was taken, see DateTimeOriginal field. (DateTime, 0x132, DefaultValue::None, d_datetime, unit![], "File change date and time"), (Artist, 0x13b, DefaultValue::None, d_default, unit![], "Person who created the image"), (WhitePoint, 0x13e, DefaultValue::None, d_decimal, unit![], "White point chromaticity"), (PrimaryChromaticities, 0x13f, DefaultValue::None, d_decimal, unit![], "Chromaticities of primaries"), // Not referenced in Exif. (TileOffsets, 0x144, DefaultValue::None, d_default, unit![], "Tiled image data location"), // Not referenced in Exif. (TileByteCounts, 0x145, DefaultValue::None, d_default, unit![], "Bytes per compressed tile"), (JPEGInterchangeFormat, 0x201, DefaultValue::None, d_default, unit![], "Offset to JPEG SOI"), (JPEGInterchangeFormatLength, 0x202, DefaultValue::None, d_default, unit![], "Bytes of JPEG data"), (YCbCrCoefficients, 0x211, DefaultValue::Unspecified, d_decimal, unit![], "Color space transformation matrix coefficients"), (YCbCrSubSampling, 0x212, DefaultValue::None, d_ycbcrsubsamp, unit![], "Subsampling ratio of Y to C"), (YCbCrPositioning, 0x213, DefaultValue::Short(&[1]), d_ycbcrpos, unit![], "Y and C positioning"), (ReferenceBlackWhite, 0x214, DefaultValue::ContextDependent, d_decimal, unit![], "Pair of black and white reference values"), (Copyright, 0x8298, DefaultValue::None, d_default, unit![], "Copyright holder"), // Exif IFD attributes [EXIF23 4.6.5 Table 7 and 4.6.8 Table 18]. |Context::Exif| (ExposureTime, 0x829a, DefaultValue::None, d_exptime, unit!["s"], "Exposure time"), (FNumber, 0x829d, DefaultValue::None, d_decimal, // F-number is dimensionless, but usually prefixed with "F" in Japan, // "f/" in the U.S., and so on. unit!["f/", V], "F number"), (ExposureProgram, 0x8822, DefaultValue::None, d_expprog, unit![], "Exposure program"), (SpectralSensitivity, 0x8824, DefaultValue::None, d_default, unit![], "Spectral sensitivity"), /// Sensitivity of the device. /// /// The value may be represented by standard output sensitivity (SOS), /// recommended exposure index (REI), or ISO speed. /// What is stored is designated by SensitivityType field. /// /// This field is 16-bit and may be saturated. For 32-bit values, /// see StandardOutputSensitivity, RecommendedExposureIndex, /// ISOSpeed, ISOSpeedLatitudeyyy, and ISOSpeedLatitudezzz fields. (PhotographicSensitivity, 0x8827, DefaultValue::None, d_default, unit![], "Photographic sensitivity"), (OECF, 0x8828, DefaultValue::None, d_default, unit![], "Optoelectric conversion factor"), (SensitivityType, 0x8830, DefaultValue::None, d_sensitivitytype, unit![], "Sensitivity type"), (StandardOutputSensitivity, 0x8831, DefaultValue::None, d_default, unit![], "Standard output sensitivity"), (RecommendedExposureIndex, 0x8832, DefaultValue::None, d_default, unit![], "Recommended exposure index"), (ISOSpeed, 0x8833, DefaultValue::None, d_default, unit![], "ISO speed"), (ISOSpeedLatitudeyyy, 0x8834, DefaultValue::None, d_default, unit![], "ISO speed latitude yyy"), (ISOSpeedLatitudezzz, 0x8835, DefaultValue::None, d_default, unit![], "ISO speed latitude zzz"), // The absence of this field means non-conformance to Exif, so the default // value specified in the standard (e.g., "0231") should not apply. (ExifVersion, 0x9000, DefaultValue::None, d_exifver, unit![], "Exif version"), /// Date and time when the original image was generated (e.g., /// the picture was taken by a camera). (DateTimeOriginal, 0x9003, DefaultValue::None, d_datetime, unit![], "Date and time of original data generation"), /// Date and time when the image was stored as digital data. /// If a picture is taken by a film camera and then digitized later, /// this value will be different from DateTimeOriginal field. (DateTimeDigitized, 0x9004, DefaultValue::None, d_datetime, unit![], "Date and time of digital data generation"), /// Timezone offset for DateTime field. (OffsetTime, 0x9010, DefaultValue::None, d_default, unit![], "Offset data of DateTime"), /// Timezone offset for DateTimeOriginal field. (OffsetTimeOriginal, 0x9011, DefaultValue::None, d_default, unit![], "Offset data of DateTimeOriginal"), /// Timezone offset for DateTimeDigitized field. (OffsetTimeDigitized, 0x9012, DefaultValue::None, d_default, unit![], "Offset data of DateTimeDigitized"), (ComponentsConfiguration, 0x9101, DefaultValue::ContextDependent, d_cpntcfg, unit![], "Meaning of each component"), (CompressedBitsPerPixel, 0x9102, DefaultValue::None, d_decimal, unit![], "Image compression mode"), (ShutterSpeedValue, 0x9201, DefaultValue::None, d_decimal, unit!["EV"], "Shutter speed"), (ApertureValue, 0x9202, DefaultValue::None, d_decimal, unit!["EV"], "Aperture"), (BrightnessValue, 0x9203, DefaultValue::None, d_optdecimal, unit!["EV"], "Brightness"), (ExposureBiasValue, 0x9204, DefaultValue::None, d_decimal, unit!["EV"], "Exposure bias"), (MaxApertureValue, 0x9205, DefaultValue::None, d_decimal, unit!["EV"], "Maximum lens aperture"), (SubjectDistance, 0x9206, DefaultValue::None, d_subjdist, unit!["m"], "Subject distance"), (MeteringMode, 0x9207, DefaultValue::Short(&[0]), d_metering, unit![], "Metering mode"), (LightSource, 0x9208, DefaultValue::Short(&[0]), d_lightsrc, unit![], "Light source"), (Flash, 0x9209, DefaultValue::Unspecified, d_flash, unit![], "Flash"), (FocalLength, 0x920a, DefaultValue::None, d_decimal, unit!["mm"], "Lens focal length"), (SubjectArea, 0x9214, DefaultValue::None, d_subjarea, unit![], "Subject area"), (MakerNote, 0x927c, DefaultValue::None, d_default, unit![], "Manufacturer notes"), (UserComment, 0x9286, DefaultValue::None, d_default, unit![], "User comments"), /// Subseconds for DateTime field. (SubSecTime, 0x9290, DefaultValue::None, d_default, unit![], "DateTime subseconds"), /// Subseconds for DateTimeOriginal field. (SubSecTimeOriginal, 0x9291, DefaultValue::None, d_default, unit![], "DateTimeOriginal subseconds"), /// Subseconds for DateTimeDigitized field. (SubSecTimeDigitized, 0x9292, DefaultValue::None, d_default, unit![], "DateTimeDigitized subseconds"), (Temperature, 0x9400, DefaultValue::None, d_optdecimal, unit!["degC"], "Temperature"), (Humidity, 0x9401, DefaultValue::None, d_optdecimal, unit!["%"], "Humidity"), (Pressure, 0x9402, DefaultValue::None, d_optdecimal, unit!["hPa"], "Pressure"), (WaterDepth, 0x9403, DefaultValue::None, d_optdecimal, unit!["m"], "Water depth"), (Acceleration, 0x9404, DefaultValue::None, d_optdecimal, unit!["mGal"], "Acceleration"), (CameraElevationAngle, 0x9405, DefaultValue::None, d_optdecimal, unit!["deg"], "Camera elevation angle"), (FlashpixVersion, 0xa000, DefaultValue::Undefined(b"0100"), d_exifver, unit![], "Supported Flashpix version"), (ColorSpace, 0xa001, DefaultValue::Unspecified, d_cspace, unit![], "Color space information"), (PixelXDimension, 0xa002, DefaultValue::None, d_default, unit!["pixels"], "Valid image width"), (PixelYDimension, 0xa003, DefaultValue::Unspecified, d_default, unit!["pixels"], "Valid image height"), (RelatedSoundFile, 0xa004, DefaultValue::None, d_default, unit![], "Related audio file"), (FlashEnergy, 0xa20b, DefaultValue::None, d_decimal, unit!["BCPS"], "Flash energy"), (SpatialFrequencyResponse, 0xa20c, DefaultValue::None, d_default, unit![], "Spatial frequency response"), (FocalPlaneXResolution, 0xa20e, DefaultValue::None, d_decimal, unit![V, " pixels per ", Tag::FocalPlaneResolutionUnit], "Focal plane X resolution"), (FocalPlaneYResolution, 0xa20f, DefaultValue::None, d_decimal, unit![V, " pixels per ", Tag::FocalPlaneResolutionUnit], "Focal plane Y resolution"), /// Unit of FocalPlaneXResolution and FocalPlaneYResolution fields. (FocalPlaneResolutionUnit, 0xa210, DefaultValue::Short(&[2]), d_resunit, unit![], "Focal plane resolution unit"), (SubjectLocation, 0xa214, DefaultValue::None, d_subjarea, unit![], "Subject location"), (ExposureIndex, 0xa215, DefaultValue::None, d_decimal, unit![], "Exposure index"), (SensingMethod, 0xa217, DefaultValue::None, d_sensingmethod, unit![], "Sensing method"), (FileSource, 0xa300, DefaultValue::Undefined(&[3]), d_filesrc, unit![], "File source"), (SceneType, 0xa301, DefaultValue::Undefined(&[1]), d_scenetype, unit![], "Scene type"), (CFAPattern, 0xa302, DefaultValue::None, d_default, unit![], "CFA pattern"), (CustomRendered, 0xa401, DefaultValue::Short(&[0]), d_customrendered, unit![], "Custom image processing"), (ExposureMode, 0xa402, DefaultValue::None, d_expmode, unit![], "Exposure mode"), (WhiteBalance, 0xa403, DefaultValue::None, d_whitebalance, unit![], "White balance"), (DigitalZoomRatio, 0xa404, DefaultValue::None, d_dzoomratio, unit![], "Digital zoom ratio"), (FocalLengthIn35mmFilm, 0xa405, DefaultValue::None, d_focallen35, unit!["mm"], "Focal length in 35 mm film"), (SceneCaptureType, 0xa406, DefaultValue::Short(&[0]), d_scenecaptype, unit![], "Scene capture type"), (GainControl, 0xa407, DefaultValue::None, d_gainctrl, unit![], "Gain control"), (Contrast, 0xa408, DefaultValue::Short(&[0]), d_contrast, unit![], "Contrast"), (Saturation, 0xa409, DefaultValue::Short(&[0]), d_saturation, unit![], "Saturation"), (Sharpness, 0xa40a, DefaultValue::Short(&[0]), d_sharpness, unit![], "Sharpness"), (DeviceSettingDescription, 0xa40b, DefaultValue::None, d_default, unit![], "Device settings description"), (SubjectDistanceRange, 0xa40c, DefaultValue::None, d_subjdistrange, unit![], "Subject distance range"), (ImageUniqueID, 0xa420, DefaultValue::None, d_default, unit![], "Unique image ID"), (CameraOwnerName, 0xa430, DefaultValue::None, d_default, unit![], "Camera owner name"), (BodySerialNumber, 0xa431, DefaultValue::None, d_default, unit![], "Body serial number"), (LensSpecification, 0xa432, DefaultValue::None, d_lensspec, unit![], "Lens specification"), (LensMake, 0xa433, DefaultValue::None, d_default, unit![], "Lens make"), (LensModel, 0xa434, DefaultValue::None, d_default, unit![], "Lens model"), (LensSerialNumber, 0xa435, DefaultValue::None, d_default, unit![], "Lens serial number"), (CompositeImage, 0xa460, DefaultValue::Short(&[0]), d_cpstimg, unit![], "Composite image"), (SourceImageNumberOfCompositeImage, 0xa461, DefaultValue::None, d_numcpstimg, unit![], "Source image number of composite image"), (SourceExposureTimesOfCompositeImage, 0xa462, DefaultValue::None, d_default, unit![], "Source exposure times of composite image"), (Gamma, 0xa500, DefaultValue::None, d_decimal, unit![], "Gamma"), // GPS attributes [EXIF23 4.6.6 Table 15 and 4.6.8 Table 19]. |Context::Gps| // Depends on the Exif version. (GPSVersionID, 0x0, DefaultValue::ContextDependent, d_gpsver, unit![], "GPS tag version"), (GPSLatitudeRef, 0x1, DefaultValue::None, d_gpslatlongref, unit![], "North or south latitude"), (GPSLatitude, 0x2, DefaultValue::None, d_gpsdms, unit![Tag::GPSLatitudeRef], "Latitude"), (GPSLongitudeRef, 0x3, DefaultValue::None, d_gpslatlongref, unit![], "East or West Longitude"), (GPSLongitude, 0x4, DefaultValue::None, d_gpsdms, unit![Tag::GPSLongitudeRef], "Longitude"), (GPSAltitudeRef, 0x5, DefaultValue::Byte(&[0]), d_gpsaltref, unit![], "Altitude reference"), (GPSAltitude, 0x6, DefaultValue::None, d_decimal, unit![V, " meters ", Tag::GPSAltitudeRef], "Altitude"), (GPSTimeStamp, 0x7, DefaultValue::None, d_gpstimestamp, unit![], "GPS time (atomic clock)"), (GPSSatellites, 0x8, DefaultValue::None, d_default, unit![], "GPS satellites used for measurement"), (GPSStatus, 0x9, DefaultValue::None, d_gpsstatus, unit![], "GPS receiver status"), (GPSMeasureMode, 0xa, DefaultValue::None, d_gpsmeasuremode, unit![], "GPS measurement mode"), (GPSDOP, 0xb, DefaultValue::None, d_decimal, unit![], "Measurement precision"), (GPSSpeedRef, 0xc, DefaultValue::Ascii(&[b"K"]), d_gpsspeedref, unit![], "Speed unit"), (GPSSpeed, 0xd, DefaultValue::None, d_decimal, unit![Tag::GPSSpeedRef], "Speed of GPS receiver"), (GPSTrackRef, 0xe, DefaultValue::Ascii(&[b"T"]), d_gpsdirref, unit![], "Reference for direction of movement"), (GPSTrack, 0xf, DefaultValue::None, d_decimal, unit![V, " degrees in ", Tag::GPSTrackRef], "Direction of movement"), (GPSImgDirectionRef, 0x10, DefaultValue::Ascii(&[b"T"]), d_gpsdirref, unit![], "Reference for direction of image"), (GPSImgDirection, 0x11, DefaultValue::None, d_decimal, unit![V, " degrees in ", Tag::GPSImgDirectionRef], "Direction of image"), (GPSMapDatum, 0x12, DefaultValue::None, d_default, unit![], "Geodetic survey data used"), (GPSDestLatitudeRef, 0x13, DefaultValue::None, d_gpslatlongref, unit![], "Reference for latitude of destination"), (GPSDestLatitude, 0x14, DefaultValue::None, d_gpsdms, unit![Tag::GPSDestLatitudeRef], "Latitude of destination"), (GPSDestLongitudeRef, 0x15, DefaultValue::None, d_gpslatlongref, unit![], "Reference for longitude of destination"), (GPSDestLongitude, 0x16, DefaultValue::None, d_gpsdms, unit![Tag::GPSDestLongitudeRef], "Longitude of destination"), (GPSDestBearingRef, 0x17, DefaultValue::Ascii(&[b"T"]), d_gpsdirref, unit![], "Reference for bearing of destination"), (GPSDestBearing, 0x18, DefaultValue::None, d_decimal, unit![V, " degrees in ", Tag::GPSDestBearingRef], "Bearing of destination"), (GPSDestDistanceRef, 0x19, DefaultValue::Ascii(&[b"K"]), d_gpsdistref, unit![], "Reference for distance to destination"), (GPSDestDistance, 0x1a, DefaultValue::None, d_decimal, unit![Tag::GPSDestDistanceRef], "Distance to destination"), (GPSProcessingMethod, 0x1b, DefaultValue::None, d_ascii_in_undef, unit![], "Name of GPS processing method"), (GPSAreaInformation, 0x1c, DefaultValue::None, d_default, unit![], "Name of GPS area"), (GPSDateStamp, 0x1d, DefaultValue::None, d_gpsdatestamp, unit![], "GPS date"), (GPSDifferential, 0x1e, DefaultValue::None, d_gpsdifferential, unit![], "GPS differential correction"), (GPSHPositioningError, 0x1f, DefaultValue::None, d_decimal, unit!["m"], "Horizontal positioning error"), // Interoperability attributes [EXIF23 4.6.7 Table 16 and 4.6.8 Table 20] // [DCF20 4.4.5.3, 4.5.4.3, and 4.6.4.3]. |Context::Interop| (InteroperabilityIndex, 0x1, DefaultValue::None, d_default, unit![], "Interoperability identification"), (InteroperabilityVersion, 0x2, DefaultValue::None, d_interopver, unit![], "Interoperability version"), (RelatedImageFileFormat, 0x1000, DefaultValue::None, d_default, unit![], "Related image file format"), (RelatedImageWidth, 0x1001, DefaultValue::None, d_default, unit!["pixels"], "Related image width"), (RelatedImageLength, 0x1002, DefaultValue::None, d_default, unit!["pixels"], "Related image height"), ); // For Value::display_as(). pub fn display_value_as<'a>(value: &'a Value, tag: Tag) -> value::Display<'a> { match get_tag_info(tag) { Some(ti) => value::Display { fmt: ti.dispval, value: value }, None => value::Display { fmt: d_default, value: value }, } } // Compression (TIFF 0x103) fn d_compression(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "uncompressed", Some(2) => "Modified Huffman", Some(6) => "JPEG", Some(32773) => "PackBits", _ => return d_reserved(w, value, "compression"), }; w.write_str(s) } // PhotometricInterpretation (TIFF 0x106) fn d_photointp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "white is zero", Some(1) => "black is zero", Some(2) => "RGB", Some(3) => "palette color", Some(4) => "transparency mask", Some(6) => "YCbCr", _ => return d_reserved(w, value, "photometric interpretation"), }; w.write_str(s) } // Orientation (TIFF 0x112) fn d_orientation(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "row 0 at top and column 0 at left", Some(2) => "row 0 at top and column 0 at right", Some(3) => "row 0 at bottom and column 0 at right", Some(4) => "row 0 at bottom and column 0 at left", Some(5) => "row 0 at left and column 0 at top", Some(6) => "row 0 at right and column 0 at top", Some(7) => "row 0 at right and column 0 at bottom", Some(8) => "row 0 at left and column 0 at bottom", _ => return d_reserved(w, value, "orientation"), }; w.write_str(s) } // PlanarConfiguration (TIFF 0x11c) fn d_planarcfg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "chunky", Some(2) => "planar", _ => return d_reserved(w, value, "planar configuration"), }; w.write_str(s) } // ResolutionUnit (TIFF 0x128) // FocalPlaneResolutionUnit (Exif 0xa210) fn d_resunit(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "no absolute unit", Some(2) => "inch", Some(3) => "cm", _ => return d_reserved(w, value, "resolution unit"), }; w.write_str(s) } // DateTime (TIFF 0x132), DateTimeOriginal (Exif 0x9003), and // DateTimeDigitized (Exif 0x9004) fn d_datetime(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { if let Some(dt) = value.ascii().and_then(|x| x.first()) { match crate::tiff::DateTime::from_ascii(dt) { Ok(dt) => return write!(w, "{}", dt), Err(Error::BlankValue(_)) => return w.write_str("unknown"), _ => {}, } } d_default(w, value) } // YCbCrSubSampling (TIFF 0x212) fn d_ycbcrsubsamp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { // 0 is used to go to d_default in the match below. let horiz = value.get_uint(0).unwrap_or(0); let vert = value.get_uint(1).unwrap_or(0); let s = match (horiz, vert) { (1, 1) => "full horizontally, full vertically (4:4:4)", (2, 1) => "half horizontally, full vertically (4:2:2)", (2, 2) => "half horizontally, half vertically (4:2:0)", (4, 1) => "quarter horizontally, full vertically (4:1:1)", (4, 2) => "quarter horizontally, half vertically", (4, 4) => "quarter horizontally, quarter vertically", _ => return d_default(w, value), }; w.write_str(s) } // YCbCrPositioning (TIFF 0x213) fn d_ycbcrpos(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "centered", Some(2) => "co-sited", _ => return d_reserved(w, value, "YCbCr positioning"), }; w.write_str(s) } // ExposureTime (Exif 0x829a) fn d_exptime(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { if let Some(et) = value.rational().and_then(|x| x.first()) { if et.num >= et.denom { return write!(w, "{}", et.to_f64()); } else if et.num != 0 { return write!(w, "1/{}", et.denom as f64 / et.num as f64); } } d_default(w, value) } // ExposureProgram (Exif 0x8822) fn d_expprog(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "not defined", Some(1) => "manual", Some(2) => "normal program", Some(3) => "aperture priority", Some(4) => "shutter priority", Some(5) => "creative program", Some(6) => "action program", Some(7) => "portrait mode", Some(8) => "landscape mode", _ => return d_reserved(w, value, "exposure program"), }; w.write_str(s) } // SensitivityType (Exif 0x8830) fn d_sensitivitytype(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "unknown", Some(1) => "SOS", Some(2) => "REI", Some(3) => "ISO speed", Some(4) => "SOS/REI", Some(5) => "SOS/ISO speed", Some(6) => "REI/ISO speed", Some(7) => "SOS/REI/ISO speed", _ => return d_reserved(w, value, "sensitivity type"), }; w.write_str(s) } // ExifVersion (Exif 0x9000), FlashpixVersion (Exif 0xa000) fn d_exifver(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { if let Some(s) = value.undefined().filter(|s| s.len() == 4) { if let Ok(major) = atou16(&s[0..2]) { if let Ok(minor) = atou16(&s[2..4]) { if minor % 10 == 0 { return write!(w, "{}.{}", major, minor / 10); } else { return write!(w, "{}.{:02}", major, minor); } } } } d_default(w, value) } // ComponentsConfiguration (Exif 0x9101) fn d_cpntcfg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match value.undefined() { Some(s) => s.iter().try_for_each(|x| match x { 0 => w.write_char('_'), 1 => w.write_char('Y'), 2 => w.write_str("Cb"), 3 => w.write_str("Cr"), 4 => w.write_char('R'), 5 => w.write_char('G'), 6 => w.write_char('B'), _ => w.write_char('?'), }), None => d_default(w, value), } } // SubjectDistance (Exif 0x9206) fn d_subjdist(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { if let Some(dist) = value.rational().and_then(|x| x.first()) { if dist.num == 0 { return w.write_str("unknown"); } else if dist.num == 0xffffffff { return w.write_str("infinity"); } } d_decimal(w, value) } // MeteringMode (Exif 0x9207) fn d_metering(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "unknown", Some(1) => "average", Some(2) => "center-weighted average", Some(3) => "spot", Some(4) => "multi-spot", Some(5) => "pattern", Some(6) => "partial", Some(255) => "other", _ => return d_reserved(w, value, "metering mode"), }; w.write_str(s) } // LightSource (Exif 0x9208) fn d_lightsrc(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "unknown", Some(1) => "daylight", Some(2) => "fluorescent", Some(3) => "tungsten", Some(4) => "flash", Some(9) => "fine weather", Some(10) => "cloudy weather", Some(11) => "shade", Some(12) => "daylight fluorescent (D 5700-7100K)", Some(13) => "day white fluorescent (N 4600-5500K)", Some(14) => "cool white fluorescent (W 3800-4500K)", Some(15) => "white fluorescent (WW 3250-3800K)", Some(16) => "warm white fluorescent (L 2600-3250K)", Some(17) => "standard light A", Some(18) => "standard light B", Some(19) => "standard light C", Some(20) => "D55", Some(21) => "D65", Some(22) => "D75", Some(23) => "D50", Some(24) => "ISO studio tungsten", Some(255) => "other", _ => return d_reserved(w, value, "light source"), }; w.write_str(s) } // Flash (Exif 0x9209) fn d_flash(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { const FIRED: &[&str] = &["not fired", "fired"]; const RETURN: &[&str] = &[ ", no return light detection function", ", return light status 1 (reserved)", ", return light not detected", ", return light detected", ]; const AUTO: &[&str] = &[ ", auto mode 0 (unknown)", ", forced", ", suppressed", ", auto"]; const FUNCTION: &[&str] = &["", ", no function present"]; const RED_EYE: &[&str] = &["", ", red-eye reduction"]; if let Some(v) = value.get_uint(0) { write!(w, "{}{}{}{}{}{}", FIRED[v as usize & 1], RETURN[v as usize >> 1 & 3], AUTO[v as usize >> 3 & 3], FUNCTION[v as usize >> 5 & 1], RED_EYE[v as usize >> 6 & 1], if v >> 7 != 0 { ", unknown MSB bits" } else { "" }) } else { d_default(w, value) } } // SubjectArea (Exif 0x9214), SubjectLocation (Exif 0xa214) // Only (x, y) case is valid for SubjectLocation. fn d_subjarea(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { if let Some(x) = value.get_uint(0) { if let Some(y) = value.get_uint(1) { if let Some(d) = value.get_uint(2) { if let Some(h) = value.get_uint(3) { return write!(w, "rectangle (x={}, y={}, w={}, h={})", x, y, d, h); } return write!(w, "circle (x={}, y={}, d={})", x, y, d); } return write!(w, "point (x={}, y={})", x, y); } } d_default(w, value) } // Rational/SRational with 0xffffffff being unknown. // BrightnessValue (Exif 0x9203), // Temperature (Exif 0x9400), Humidity (Exif 0x9401), // Pressure (Exif 0x9402), WaterDepth (Exif 0x9403), // Acceleration (Exif 0x9404), CameraElevationAngle (Exif 0x9405) fn d_optdecimal(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Rational(ref v) if v.len() > 0 => if v[0].denom != 0xffffffff { write!(w, "{}", v[0].to_f64()) } else { w.write_str("unknown") }, Value::SRational(ref v) if v.len() > 0 => if v[0].denom != -1 { write!(w, "{}", v[0].to_f64()) } else { w.write_str("unknown") }, _ => d_decimal(w, value), } } // ColorSpace (Exif 0xa001) fn d_cspace(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "sRGB", Some(0xffff) => "uncalibrated", _ => return d_reserved(w, value, "color space"), }; w.write_str(s) } // SensingMethod (Exif 0xa217) fn d_sensingmethod(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "not defined", Some(2) => "one-chip color area sensor", Some(3) => "two-chip color area sensor", Some(4) => "three-chip color area sensor", Some(5) => "color sequential area sensor", Some(7) => "trilinear sensor", Some(8) => "color sequential linear sensor", _ => return d_reserved(w, value, "sensing method"), }; w.write_str(s) } // FileSource (Exif 0xa300) fn d_filesrc(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.undefined().and_then(|x| x.first().copied()) { Some(0) => "others", Some(1) => "transparency scanner", Some(2) => "reflective scanner", Some(3) => "digital still camera", _ => return d_reserved(w, value, "file source"), }; w.write_str(s) } // SceneType (Exif 0xa301) fn d_scenetype(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.undefined().and_then(|x| x.first().copied()) { Some(1) => "directly photographed image", _ => return d_reserved(w, value, "scene type"), }; w.write_str(s) } // CustomRendered (Exif 0xa401) fn d_customrendered(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "normal process", Some(1) => "custom process", _ => return d_reserved(w, value, "custom rendered"), }; w.write_str(s) } // ExposureMode (Exif 0xa402) fn d_expmode(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "auto exposure", Some(1) => "manual exposure", Some(2) => "auto bracket", _ => return d_reserved(w, value, "exposure mode"), }; w.write_str(s) } // WhiteBalance (Exif 0xa403) fn d_whitebalance(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "auto white balance", Some(1) => "manual white balance", _ => return d_reserved(w, value, "white balance mode"), }; w.write_str(s) } // DigitalZoomRatio (Exif 0xa404) fn d_dzoomratio(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { if value.rational().and_then(|x| x.first()).map(|x| x.num) == Some(0) { return w.write_str("unused"); } d_decimal(w, value) } // FocalLengthIn35mmFilm (Exif 0xa405) fn d_focallen35(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match value.get_uint(0) { Some(0) => w.write_str("unknown"), _ => d_default(w, value), } } // SceneCaptureType (Exif 0xa406) fn d_scenecaptype(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "standard", Some(1) => "landscape", Some(2) => "portrait", Some(3) => "night scene", _ => return d_reserved(w, value, "scene capture type"), }; w.write_str(s) } // GainControl (Exif 0xa407) fn d_gainctrl(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "none", Some(1) => "low gain up", Some(2) => "high gain up", Some(3) => "low gain down", Some(4) => "high gain down", _ => return d_reserved(w, value, "gain control"), }; w.write_str(s) } // Contrast (Exif 0xa408) fn d_contrast(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "normal", Some(1) => "soft", Some(2) => "hard", _ => return d_reserved(w, value, "contrast processing"), }; w.write_str(s) } // Saturation (Exif 0xa409) fn d_saturation(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "normal", Some(1) => "low saturation", Some(2) => "high saturation", _ => return d_reserved(w, value, "saturation processing"), }; w.write_str(s) } // Sharpness (Exif 0xa40a) fn d_sharpness(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "normal", Some(1) => "soft", Some(2) => "hard", _ => return d_reserved(w, value, "sharpness processing"), }; w.write_str(s) } // SubjectDistanceRange (Exif 0xa40c) fn d_subjdistrange(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "unknown", Some(1) => "macro", Some(2) => "close view", Some(3) => "distant view", _ => return d_reserved(w, value, "subject distance range"), }; w.write_str(s) } // LensSpecification (Exif 0xa432) fn d_lensspec(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match value.rational().and_then(|x| x.get(..4)) { // There are several notations: "F1.4" in Japan, "f/1.4" // in the U.S., and so on. Some(s) => write!(w, "{}-{} mm, f/{}-{}", s[0].to_f64(), s[1].to_f64(), s[2].to_f64(), s[3].to_f64()), _ => d_default(w, value), } } // CompositeImage (Exif 0xa460) fn d_cpstimg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "unknown", Some(1) => "non-composite", Some(2) => "composite (general)", Some(3) => "composite (at the moment of shooting)", _ => return d_reserved(w, value, "composite image"), }; w.write_str(s) } // SourceImageNumberOfCompositeImage (Exif 0xa461) fn d_numcpstimg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match (value.get_uint(0), value.get_uint(1)) { (Some(t), Some(u)) => write!(w, "total {}, used {}", t, u), _ => d_default(w, value), } } // GPSVersionID (GPS 0x0) fn d_gpsver(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match value.byte().and_then(|x| x.get(..4)) { Some(s) => write!(w, "{}.{}.{}.{}", s[0], s[1], s[2], s[3]), _ => d_default(w, value), } } // GPSLatitudeRef (GPS 0x1), GPSLongitudeRef (GPS 0x3) // GPSDestLatitudeRef (GPS 0x13), GPSDestLongitudeRef (GPS 0x15) fn d_gpslatlongref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match value.ascii().and_then(|x| x.first()) { Some([c]) if c.is_ascii_uppercase() => w.write_char(*c as char), _ => d_default(w, value), } } // GPSLatitude (GPS 0x2), GPSLongitude (GPS 0x4), // GPSDestLatitude (GPS 0x14), GPSDestLongitude (GPS 0x16) fn d_gpsdms(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match value.rational().and_then(|x| x.get(..3)) { Some(s) => write!(w, "{} deg {} min {} sec", s[0].to_f64(), s[1].to_f64(), s[2].to_f64()), _ => d_default(w, value), } } // GPSAltitudeRef (GPS 0x5) fn d_gpsaltref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "above sea level", Some(1) => "below sea level", _ => return d_reserved(w, value, "GPS altitude ref"), }; w.write_str(s) } // GPSTimeStamp (GPS 0x7) fn d_gpstimestamp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match value.rational().and_then(|x| x.get(..3)) { Some(s) => { let (h, m, s) = (s[0].to_f64(), s[1].to_f64(), s[2].to_f64()); write!(w, "{}{}:{}{}:{}{}", if h < 10.0 { "0" } else { "" }, h, if m < 10.0 { "0" } else { "" }, m, if s < 10.0 { "0" } else { "" }, s) }, _ => d_default(w, value), } } // GPSStatus (GPS 0x9) fn d_gpsstatus(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.ascii().and_then(|x| x.first()) { Some(b"A") => "measurement in progress", Some(b"V") => "measurement interrupted", _ => return d_reserved(w, value, "GPS status"), }; w.write_str(s) } // GPSMeasureMode (GPS 0xa) fn d_gpsmeasuremode(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.ascii().and_then(|x| x.first()) { Some(b"2") => "2-dimensional measurement", Some(b"3") => "3-dimensional measurement", _ => return d_reserved(w, value, "GPS measurement mode"), }; w.write_str(s) } // GPSSpeedRef (GPS 0xc) fn d_gpsspeedref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.ascii().and_then(|x| x.first()) { Some(b"K") => "km/h", Some(b"M") => "mph", Some(b"N") => "knots", _ => return d_reserved(w, value, "GPS speed ref"), }; w.write_str(s) } // GPSTrackRef (GPS 0xe), GPSImgDirectionRef (GPS 0x10), // GPSDestBearingRef (GPS 0x17) fn d_gpsdirref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.ascii().and_then(|x| x.first()) { Some(b"T") => "true direction", Some(b"M") => "magnetic direction", _ => return d_reserved(w, value, "GPS direction ref"), }; w.write_str(s) } // GPSDestDistanceRef (GPS 0x19) fn d_gpsdistref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.ascii().and_then(|x| x.first()) { Some(b"K") => "km", Some(b"M") => "miles", Some(b"N") => "nautical miles", _ => return d_reserved(w, value, "GPS distance ref"), }; w.write_str(s) } // GPSDateStamp (GPS 0x1d) fn d_gpsdatestamp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { if let Some(data) = value.ascii().and_then(|x| x.first()) { if data.len() >= 10 && data[4] == b':' && data[7] == b':' { if let Ok(year) = atou16(&data[0..4]) { if let Ok(month) = atou16(&data[5..7]) { if let Ok(day) = atou16(&data[8..10]) { return write!(w, "{:04}-{:02}-{:02}", year, month, day); } } } } } d_default(w, value) } // GPSDifferential (GPS 0x1e) fn d_gpsdifferential(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "no differential correction", Some(1) => "differential correction applied", _ => return d_reserved(w, value, "GPS differential correction"), }; w.write_str(s) } // InteroperabilityVersion (Interoperability 0x2) fn d_interopver(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { if let Some(s) = value.undefined().filter(|s| s.len() == 4) { if let Ok(major) = atou16(&s[0..2]) { if let Ok(minor) = atou16(&s[2..4]) { return write!(w, "{}.{:02}", major, minor); } } } d_default(w, value) } fn d_ascii_in_undef(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Undefined(ref v, _) => d_sub_ascii(w, v), _ => d_default(w, value), } } fn d_decimal(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Rational(ref v) => d_sub_comma(w, v.iter().map(|x| x.to_f64())), Value::SRational(ref v) => d_sub_comma(w, v.iter().map(|x| x.to_f64())), _ => d_default(w, value), } } #[inline(never)] fn d_reserved(w: &mut dyn fmt::Write, value: &Value, name: &str) -> fmt::Result { write!(w, "[reserved {} ", name)?; d_default(w, value)?; w.write_char(']') } fn d_default(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Byte(ref v) => d_sub_comma(w, v), Value::Ascii(ref v) => d_sub_comma(w, v.iter().map(|x| AsciiDisplay(x))), Value::Short(ref v) => d_sub_comma(w, v), Value::Long(ref v) => d_sub_comma(w, v), Value::Rational(ref v) => d_sub_comma(w, v), Value::SByte(ref v) => d_sub_comma(w, v), Value::Undefined(ref v, _) => d_sub_hex(w, v), Value::SShort(ref v) => d_sub_comma(w, v), Value::SLong(ref v) => d_sub_comma(w, v), Value::SRational(ref v) => d_sub_comma(w, v), Value::Float(ref v) => d_sub_comma(w, v), Value::Double(ref v) => d_sub_comma(w, v), Value::Unknown(t, c, o) => write!(w, "unknown value (type={}, count={}, offset={:#x})", t, c, o), } } fn d_sub_comma(w: &mut dyn fmt::Write, itit: I) -> fmt::Result where I: IntoIterator, T: fmt::Display { let mut first = true; for x in itit { match first { true => write!(w, "{}", x), false => write!(w, ", {}", x), }?; first = false; } Ok(()) } struct AsciiDisplay<'a>(&'a [u8]); impl<'a> fmt::Display for AsciiDisplay<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { d_sub_ascii(f, self.0) } } fn d_sub_hex(w: &mut dyn fmt::Write, bytes: &[u8]) -> fmt::Result { w.write_str("0x")?; for x in bytes { write!(w, "{:02x}", x)?; } Ok(()) } fn d_sub_ascii(w: &mut dyn fmt::Write, bytes: &[u8]) -> fmt::Result { w.write_char('"')?; for &c in bytes { match c { b'\\' | b'"' => { w.write_char('\\')?; w.write_char(c as char)?; }, 0x20..=0x7e => w.write_char(c as char)?, _ => write!(w, "\\x{:02x}", c)?, } } w.write_char('"') } #[cfg(test)] mod tests { use value::Rational; use super::*; // This test checks if Tag constants can be used in patterns. #[test] fn tag_constant_in_pattern() { // Destructuring, which will always work. match Tag(Context::Tiff, 0x132) { Tag(Context::Tiff, 0x132) => {}, _ => panic!("failed to match Tag"), } // Matching against a constant. Test if this compiles. match Tag(Context::Tiff, 0x132) { Tag::DateTime => {}, _ => panic!("failed to match Tag"), } } #[test] fn default_value() { assert_pat!(Tag::DateTime.default_value(), None); match Tag::BitsPerSample.default_value() { Some(Value::Short(v)) => assert_eq!(v, &[8, 8, 8]), _ => panic!(), } match Tag::XResolution.default_value() { Some(Value::Rational(v)) => { assert_eq!(v.len(), 1); assert_eq!(v[0].num, 72); assert_eq!(v[0].denom, 1); }, _ => panic!(), } match Tag::FileSource.default_value() { Some(Value::Undefined(v, _)) => assert_eq!(v, &[3]), _ => panic!(), } match Tag::GPSAltitudeRef.default_value() { Some(Value::Byte(v)) => assert_eq!(v, &[0]), _ => panic!(), } match Tag::GPSSpeedRef.default_value() { Some(Value::Ascii(v)) => assert_eq!(v, &[b"K"]), _ => panic!(), } } #[test] fn tag_fmt_display() { let tag1 = Tag(Context::Tiff, 0x132); assert_eq!(format!("{:15}", tag1), "DateTime "); assert_eq!(format!("{:>15}", tag1), " DateTime"); assert_eq!(format!("{:5.6}", tag1), "DateTi"); let tag2 = Tag(Context::Exif, 0); assert_eq!(format!("{:15}", tag2), "Tag(Exif, 0) "); assert_eq!(format!("{:>15}", tag2), " Tag(Exif, 0)"); assert_eq!(format!("{:5.6}", tag2), "Tag(Ex"); } #[test] fn disp_val_sub() { let mut buf = String::new(); d_sub_comma(&mut buf, &[0u16, 1, 2]).unwrap(); assert_eq!(buf, "0, 1, 2"); let mut buf = String::new(); d_sub_comma(&mut buf, &[Rational::from((3, 5))]).unwrap(); assert_eq!(buf, "3/5"); let mut buf = String::new(); let list = &[Rational::from((1, 2))]; d_sub_comma(&mut buf, list.iter().map(|x| x.to_f64())).unwrap(); assert_eq!(buf, "0.5"); let mut buf = String::new(); d_sub_hex(&mut buf, b"abc\x00\xff").unwrap(); assert_eq!(buf, "0x61626300ff"); let mut buf = String::new(); d_sub_ascii(&mut buf, b"a \"\\b\"\n").unwrap(); assert_eq!(buf, r#""a \"\\b\"\x0a""#); } } kamadak-exif-0.5.5/src/tiff.rs000064400000000000000000000615741046102023000142570ustar 00000000000000// // Copyright (c) 2016 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. // use std::fmt; use mutate_once::MutOnce; use crate::endian::{Endian, BigEndian, LittleEndian}; use crate::error::Error; use crate::tag::{Context, Tag, UnitPiece}; use crate::value; use crate::value::Value; use crate::value::get_type_info; use crate::util::{atou16, ctou32}; // TIFF header magic numbers [EXIF23 4.5.2]. const TIFF_BE: u16 = 0x4d4d; const TIFF_LE: u16 = 0x4949; const TIFF_FORTY_TWO: u16 = 0x002a; pub const TIFF_BE_SIG: [u8; 4] = [0x4d, 0x4d, 0x00, 0x2a]; pub const TIFF_LE_SIG: [u8; 4] = [0x49, 0x49, 0x2a, 0x00]; // Partially parsed TIFF field (IFD entry). // Value::Unknown is abused to represent a partially parsed value. // Such a value must never be exposed to the users of this library. #[derive(Debug)] pub struct IfdEntry { // When partially parsed, the value is stored as Value::Unknown. // Do not leak this field to the outside. field: MutOnce, } impl IfdEntry { pub fn ifd_num_tag(&self) -> (In, Tag) { if self.field.is_fixed() { let field = self.field.get_ref(); (field.ifd_num, field.tag) } else { let field = self.field.get_mut(); (field.ifd_num, field.tag) } } pub fn ref_field<'a>(&'a self, data: &[u8], le: bool) -> &'a Field { self.parse(data, le); self.field.get_ref() } fn into_field(self, data: &[u8], le: bool) -> Field { self.parse(data, le); self.field.into_inner() } fn parse(&self, data: &[u8], le: bool) { if !self.field.is_fixed() { let mut field = self.field.get_mut(); if le { Self::parse_value::(&mut field.value, data); } else { Self::parse_value::(&mut field.value, data); } } } // Converts a partially parsed value into a real one. fn parse_value(value: &mut Value, data: &[u8]) where E: Endian { match *value { Value::Unknown(typ, cnt, ofs) => { let (unitlen, parser) = get_type_info::(typ); if unitlen != 0 { *value = parser(data, ofs as usize, cnt as usize); } }, _ => panic!("value is already parsed"), } } } /// A TIFF/Exif field. #[derive(Debug, Clone)] pub struct Field { /// The tag of this field. pub tag: Tag, /// The index of the IFD to which this field belongs. pub ifd_num: In, /// The value of this field. pub value: Value, } /// An IFD number. /// /// The IFDs are indexed from 0. The 0th IFD is for the primary image /// and the 1st one is for the thumbnail. Two associated constants, /// `In::PRIMARY` and `In::THUMBNAIL`, are defined for them respectively. /// /// # Examples /// ``` /// use exif::In; /// assert_eq!(In::PRIMARY.index(), 0); /// assert_eq!(In::THUMBNAIL.index(), 1); /// ``` #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct In(pub u16); impl In { pub const PRIMARY: In = In(0); pub const THUMBNAIL: In = In(1); /// Returns the IFD number. #[inline] pub fn index(self) -> u16 { self.0 } } impl fmt::Display for In { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { 0 => f.pad("primary"), 1 => f.pad("thumbnail"), n => f.pad(&format!("IFD{}", n)), } } } /// Parse the Exif attributes in the TIFF format. /// /// Returns a Vec of Exif fields and a bool. /// The boolean value is true if the data is little endian. /// If an error occurred, `exif::Error` is returned. pub fn parse_exif(data: &[u8]) -> Result<(Vec, bool), Error> { let mut parser = Parser::new(); parser.parse(data)?; let (entries, le) = (parser.entries, parser.little_endian); Ok((entries.into_iter().map(|e| e.into_field(data, le)).collect(), le)) } #[derive(Debug)] pub struct Parser { pub entries: Vec, pub little_endian: bool, } impl Parser { pub fn new() -> Self { Self { entries: Vec::new(), little_endian: false } } pub fn parse(&mut self, data: &[u8]) -> Result<(), Error> { // Check the byte order and call the real parser. if data.len() < 8 { return Err(Error::InvalidFormat("Truncated TIFF header")); } match BigEndian::loadu16(data, 0) { TIFF_BE => { self.little_endian = false; self.parse_sub::(data) }, TIFF_LE => { self.little_endian = true; self.parse_sub::(data) }, _ => Err(Error::InvalidFormat("Invalid TIFF byte order")), } } fn parse_sub(&mut self, data: &[u8]) -> Result<(), Error> where E: Endian { // Parse the rest of the header (42 and the IFD offset). if E::loadu16(data, 2) != TIFF_FORTY_TWO { return Err(Error::InvalidFormat("Invalid forty two")); } let mut ifd_offset = E::loadu32(data, 4) as usize; let mut ifd_num_ck = Some(0); while ifd_offset != 0 { let ifd_num = ifd_num_ck .ok_or(Error::InvalidFormat("Too many IFDs"))?; // Limit the number of IFDs to defend against resource exhaustion // attacks. if ifd_num >= 8 { return Err(Error::InvalidFormat("Limit the IFD count to 8")); } ifd_offset = self.parse_ifd::( data, ifd_offset, Context::Tiff, ifd_num)?; ifd_num_ck = ifd_num.checked_add(1); } Ok(()) } // Parse IFD [EXIF23 4.6.2]. fn parse_ifd(&mut self, data: &[u8], offset: usize, ctx: Context, ifd_num: u16) -> Result where E: Endian { // Count (the number of the entries). if data.len() < offset || data.len() - offset < 2 { return Err(Error::InvalidFormat("Truncated IFD count")); } let count = E::loadu16(data, offset) as usize; // Array of entries. (count * 12) never overflows. if data.len() - offset - 2 < count * 12 { return Err(Error::InvalidFormat("Truncated IFD")); } for i in 0..count as usize { let tag = E::loadu16(data, offset + 2 + i * 12); let typ = E::loadu16(data, offset + 2 + i * 12 + 2); let cnt = E::loadu32(data, offset + 2 + i * 12 + 4); let valofs_at = offset + 2 + i * 12 + 8; let (unitlen, _parser) = get_type_info::(typ); let vallen = unitlen.checked_mul(cnt as usize).ok_or( Error::InvalidFormat("Invalid entry count"))?; let mut val = if vallen <= 4 { Value::Unknown(typ, cnt, valofs_at as u32) } else { let ofs = E::loadu32(data, valofs_at) as usize; if data.len() < ofs || data.len() - ofs < vallen { return Err(Error::InvalidFormat("Truncated field value")); } Value::Unknown(typ, cnt, ofs as u32) }; // No infinite recursion will occur because the context is not // recursively defined. let tag = Tag(ctx, tag); match tag { Tag::ExifIFDPointer => self.parse_child_ifd::( data, &mut val, Context::Exif, ifd_num)?, Tag::GPSInfoIFDPointer => self.parse_child_ifd::( data, &mut val, Context::Gps, ifd_num)?, Tag::InteropIFDPointer => self.parse_child_ifd::( data, &mut val, Context::Interop, ifd_num)?, _ => self.entries.push(IfdEntry { field: Field { tag: tag, ifd_num: In(ifd_num), value: val }.into()}), } } // Offset to the next IFD. if data.len() - offset - 2 - count * 12 < 4 { return Err(Error::InvalidFormat("Truncated next IFD offset")); } let next_ifd_offset = E::loadu32(data, offset + 2 + count * 12); Ok(next_ifd_offset as usize) } fn parse_child_ifd(&mut self, data: &[u8], pointer: &mut Value, ctx: Context, ifd_num: u16) -> Result<(), Error> where E: Endian { // The pointer is not yet parsed, so do it here. IfdEntry::parse_value::(pointer, data); // A pointer field has type == LONG and count == 1, so the // value (IFD offset) must be embedded in the "value offset" // element of the field. let ofs = pointer.get_uint(0).ok_or( Error::InvalidFormat("Invalid pointer"))? as usize; match self.parse_ifd::(data, ofs, ctx, ifd_num)? { 0 => Ok(()), _ => Err(Error::InvalidFormat("Unexpected next IFD")), } } } pub fn is_tiff(buf: &[u8]) -> bool { buf.starts_with(&TIFF_BE_SIG) || buf.starts_with(&TIFF_LE_SIG) } /// A struct used to parse a DateTime field. /// /// # Examples /// ``` /// # fn main() -> Result<(), Box> { /// use exif::DateTime; /// let dt = DateTime::from_ascii(b"2016:05:04 03:02:01")?; /// assert_eq!(dt.year, 2016); /// assert_eq!(dt.to_string(), "2016-05-04 03:02:01"); /// # Ok(()) } /// ``` #[derive(Debug)] pub struct DateTime { pub year: u16, pub month: u8, pub day: u8, pub hour: u8, pub minute: u8, pub second: u8, /// The subsecond data in nanoseconds. If the Exif attribute has /// more sigfinicant digits, they are rounded down. pub nanosecond: Option, /// The offset of the time zone in minutes. pub offset: Option, } impl DateTime { /// Parse an ASCII data of a DateTime field. The range of a number /// is not validated, so, for example, 13 may be returned as the month. /// /// If the value is blank, `Error::BlankValue` is returned. pub fn from_ascii(data: &[u8]) -> Result { if data == b" : : : : " || data == b" " { return Err(Error::BlankValue("DateTime is blank")); } else if data.len() < 19 { return Err(Error::InvalidFormat("DateTime too short")); } else if !(data[4] == b':' && data[7] == b':' && data[10] == b' ' && data[13] == b':' && data[16] == b':') { return Err(Error::InvalidFormat("Invalid DateTime delimiter")); } Ok(DateTime { year: atou16(&data[0..4])?, month: atou16(&data[5..7])? as u8, day: atou16(&data[8..10])? as u8, hour: atou16(&data[11..13])? as u8, minute: atou16(&data[14..16])? as u8, second: atou16(&data[17..19])? as u8, nanosecond: None, offset: None, }) } /// Parses an SubsecTime-like field. pub fn parse_subsec(&mut self, data: &[u8]) -> Result<(), Error> { let mut subsec = 0; let mut ndigits = 0; for &c in data { if c == b' ' { break; } subsec = subsec * 10 + ctou32(c)?; ndigits += 1; if ndigits >= 9 { break; } } if ndigits == 0 { self.nanosecond = None; } else { for _ in ndigits..9 { subsec *= 10; } self.nanosecond = Some(subsec); } Ok(()) } /// Parses an OffsetTime-like field. pub fn parse_offset(&mut self, data: &[u8]) -> Result<(), Error> { if data == b" : " || data == b" " { return Err(Error::BlankValue("OffsetTime is blank")); } else if data.len() < 6 { return Err(Error::InvalidFormat("OffsetTime too short")); } else if data[3] != b':' { return Err(Error::InvalidFormat("Invalid OffsetTime delimiter")); } let hour = atou16(&data[1..3])?; let min = atou16(&data[4..6])?; let offset = (hour * 60 + min) as i16; self.offset = Some(match data[0] { b'+' => offset, b'-' => -offset, _ => return Err(Error::InvalidFormat("Invalid OffsetTime sign")), }); Ok(()) } } impl fmt::Display for DateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", self.year, self.month, self.day, self.hour, self.minute, self.second) } } impl Field { /// Returns an object that implements `std::fmt::Display` for /// printing the value of this field in a tag-specific format. /// /// To print the value with the unit, call `with_unit` method on the /// returned object. It takes a parameter, which is either `()`, /// `&Field`, or `&Exif`, that provides the unit information. /// If the unit does not depend on another field, `()` can be used. /// Otherwise, `&Field` or `&Exif` should be used. /// /// # Examples /// /// ``` /// use exif::{Field, In, Tag, Value}; /// /// let xres = Field { /// tag: Tag::XResolution, /// ifd_num: In::PRIMARY, /// value: Value::Rational(vec![(72, 1).into()]), /// }; /// let resunit = Field { /// tag: Tag::ResolutionUnit, /// ifd_num: In::PRIMARY, /// value: Value::Short(vec![3]), /// }; /// assert_eq!(xres.display_value().to_string(), "72"); /// assert_eq!(resunit.display_value().to_string(), "cm"); /// // The unit of XResolution is indicated by ResolutionUnit. /// assert_eq!(xres.display_value().with_unit(&resunit).to_string(), /// "72 pixels per cm"); /// // If ResolutionUnit is not given, the default value is used. /// assert_eq!(xres.display_value().with_unit(()).to_string(), /// "72 pixels per inch"); /// assert_eq!(xres.display_value().with_unit(&xres).to_string(), /// "72 pixels per inch"); /// /// let flen = Field { /// tag: Tag::FocalLengthIn35mmFilm, /// ifd_num: In::PRIMARY, /// value: Value::Short(vec![24]), /// }; /// // The unit of the focal length is always mm, so the argument /// // has nothing to do with the result. /// assert_eq!(flen.display_value().with_unit(()).to_string(), /// "24 mm"); /// assert_eq!(flen.display_value().with_unit(&resunit).to_string(), /// "24 mm"); /// ``` #[inline] pub fn display_value(&self) -> DisplayValue { DisplayValue { tag: self.tag, ifd_num: self.ifd_num, value_display: self.value.display_as(self.tag), } } } /// Helper struct for printing a value in a tag-specific format. pub struct DisplayValue<'a> { tag: Tag, ifd_num: In, value_display: value::Display<'a>, } impl<'a> DisplayValue<'a> { #[inline] pub fn with_unit(&self, unit_provider: T) -> DisplayValueUnit<'a, T> where T: ProvideUnit<'a> { DisplayValueUnit { ifd_num: self.ifd_num, value_display: self.value_display, unit: self.tag.unit(), unit_provider: unit_provider, } } } impl<'a> fmt::Display for DisplayValue<'a> { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.value_display.fmt(f) } } /// Helper struct for printing a value with its unit. pub struct DisplayValueUnit<'a, T> where T: ProvideUnit<'a> { ifd_num: In, value_display: value::Display<'a>, unit: Option<&'static [UnitPiece]>, unit_provider: T, } impl<'a, T> fmt::Display for DisplayValueUnit<'a, T> where T: ProvideUnit<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(unit) = self.unit { assert!(!unit.is_empty()); for piece in unit { match *piece { UnitPiece::Value => self.value_display.fmt(f), UnitPiece::Str(s) => f.write_str(s), UnitPiece::Tag(tag) => if let Some(x) = self.unit_provider.get_field( tag, self.ifd_num) { x.value.display_as(tag).fmt(f) } else if let Some(x) = tag.default_value() { x.display_as(tag).fmt(f) } else { write!(f, "[{} missing]", tag) }, }? } Ok(()) } else { self.value_display.fmt(f) } } } pub trait ProvideUnit<'a>: Copy { fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field>; } impl<'a> ProvideUnit<'a> for () { fn get_field(self, _tag: Tag, _ifd_num: In) -> Option<&'a Field> { None } } impl<'a> ProvideUnit<'a> for &'a Field { fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field> { Some(self).filter(|x| x.tag == tag && x.ifd_num == ifd_num) } } #[cfg(test)] mod tests { use super::*; #[test] fn in_convert() { assert_eq!(In::PRIMARY.index(), 0); assert_eq!(In::THUMBNAIL.index(), 1); assert_eq!(In(2).index(), 2); assert_eq!(In(65535).index(), 65535); assert_eq!(In::PRIMARY, In(0)); } #[test] fn in_display() { assert_eq!(format!("{:10}", In::PRIMARY), "primary "); assert_eq!(format!("{:>10}", In::THUMBNAIL), " thumbnail"); assert_eq!(format!("{:10}", In(2)), "IFD2 "); assert_eq!(format!("{:^10}", In(65535)), " IFD65535 "); } #[test] fn truncated() { let mut data = b"MM\0\x2a\0\0\0\x08\ \0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\0".to_vec(); parse_exif(&data).unwrap(); while let Some(_) = data.pop() { parse_exif(&data).unwrap_err(); } } // Before the error is returned, the IFD is parsed multiple times // as the 0th, 1st, ..., and n-th IFDs. #[test] fn inf_loop_by_next() { let data = b"MM\0\x2a\0\0\0\x08\ \0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\x08"; assert_err_pat!(parse_exif(data), Error::InvalidFormat("Limit the IFD count to 8")); } #[test] fn inf_loop_by_exif_next() { let data = b"MM\x00\x2a\x00\x00\x00\x08\ \x00\x01\x87\x69\x00\x04\x00\x00\x00\x01\x00\x00\x00\x1a\ \x00\x00\x00\x00\ \x00\x01\x90\x00\x00\x07\x00\x00\x00\x040231\ \x00\x00\x00\x08"; assert_err_pat!(parse_exif(data), Error::InvalidFormat("Unexpected next IFD")); } #[test] fn unknown_field() { let data = b"MM\0\x2a\0\0\0\x08\ \0\x01\x01\0\xff\xff\0\0\0\x01\0\x14\0\0\0\0\0\0"; let (v, _le) = parse_exif(data).unwrap(); assert_eq!(v.len(), 1); assert_pat!(v[0].value, Value::Unknown(0xffff, 1, 0x12)); } #[test] fn date_time() { let mut dt = DateTime::from_ascii(b"2016:05:04 03:02:01").unwrap(); assert_eq!(dt.year, 2016); assert_eq!(dt.to_string(), "2016-05-04 03:02:01"); dt.parse_subsec(b"987").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 987000000); dt.parse_subsec(b"000987").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 987000); dt.parse_subsec(b"987654321").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 987654321); dt.parse_subsec(b"9876543219").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 987654321); dt.parse_subsec(b"130 ").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 130000000); dt.parse_subsec(b"0").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 0); dt.parse_subsec(b"").unwrap(); assert!(dt.nanosecond.is_none()); dt.parse_subsec(b" ").unwrap(); assert!(dt.nanosecond.is_none()); dt.parse_offset(b"+00:00").unwrap(); assert_eq!(dt.offset.unwrap(), 0); dt.parse_offset(b"+01:23").unwrap(); assert_eq!(dt.offset.unwrap(), 83); dt.parse_offset(b"+99:99").unwrap(); assert_eq!(dt.offset.unwrap(), 6039); dt.parse_offset(b"-01:23").unwrap(); assert_eq!(dt.offset.unwrap(), -83); dt.parse_offset(b"-99:99").unwrap(); assert_eq!(dt.offset.unwrap(), -6039); assert_err_pat!(dt.parse_offset(b" : "), Error::BlankValue(_)); assert_err_pat!(dt.parse_offset(b" "), Error::BlankValue(_)); } #[test] fn display_value_with_unit() { let cm = Field { tag: Tag::ResolutionUnit, ifd_num: In::PRIMARY, value: Value::Short(vec![3]), }; let cm_tn = Field { tag: Tag::ResolutionUnit, ifd_num: In::THUMBNAIL, value: Value::Short(vec![3]), }; // No unit. let exifver = Field { tag: Tag::ExifVersion, ifd_num: In::PRIMARY, value: Value::Undefined(b"0231".to_vec(), 0), }; assert_eq!(exifver.display_value().to_string(), "2.31"); assert_eq!(exifver.display_value().with_unit(()).to_string(), "2.31"); assert_eq!(exifver.display_value().with_unit(&cm).to_string(), "2.31"); // Fixed string. let width = Field { tag: Tag::ImageWidth, ifd_num: In::PRIMARY, value: Value::Short(vec![257]), }; assert_eq!(width.display_value().to_string(), "257"); assert_eq!(width.display_value().with_unit(()).to_string(), "257 pixels"); assert_eq!(width.display_value().with_unit(&cm).to_string(), "257 pixels"); // Unit tag (with a non-default value). // Unit tag is missing but the default is specified. let xres = Field { tag: Tag::XResolution, ifd_num: In::PRIMARY, value: Value::Rational(vec![(300, 1).into()]), }; assert_eq!(xres.display_value().to_string(), "300"); assert_eq!(xres.display_value().with_unit(()).to_string(), "300 pixels per inch"); assert_eq!(xres.display_value().with_unit(&cm).to_string(), "300 pixels per cm"); assert_eq!(xres.display_value().with_unit(&cm_tn).to_string(), "300 pixels per inch"); // Unit tag is missing and the default is not specified. let gpslat = Field { tag: Tag::GPSLatitude, ifd_num: In::PRIMARY, value: Value::Rational(vec![ (10, 1).into(), (0, 1).into(), (1, 10).into()]), }; assert_eq!(gpslat.display_value().to_string(), "10 deg 0 min 0.1 sec"); assert_eq!(gpslat.display_value().with_unit(()).to_string(), "10 deg 0 min 0.1 sec [GPSLatitudeRef missing]"); assert_eq!(gpslat.display_value().with_unit(&cm).to_string(), "10 deg 0 min 0.1 sec [GPSLatitudeRef missing]"); } #[test] fn no_borrow_no_move() { let resunit = Field { tag: Tag::ResolutionUnit, ifd_num: In::PRIMARY, value: Value::Short(vec![3]), }; // This fails to compile with "temporary value dropped while // borrowed" error if with_unit() borrows self. let d = resunit.display_value().with_unit(()); assert_eq!(d.to_string(), "cm"); // This fails to compile if with_unit() moves self. let d1 = resunit.display_value(); let d2 = d1.with_unit(()); assert_eq!(d1.to_string(), "cm"); assert_eq!(d2.to_string(), "cm"); } } kamadak-exif-0.5.5/src/tmacro.rs000064400000000000000000000044121046102023000146000ustar 00000000000000// // Copyright (c) 2016 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. // // Macros for testing. macro_rules! assert_ok { ($expr:expr, $value:expr) => ( match $expr { Ok(v) => assert_eq!(v, $value), r => panic!("assertion failed: unexpected {:?}", r), } ) } macro_rules! assert_pat { ($expr:expr, $pat:pat) => ( match $expr { $pat => {}, ref r => panic!("assertion failed: unexpected {:?}", r), } ) } macro_rules! assert_err_pat { ($expr:expr, $variant:pat) => ( match $expr { Err($variant) => {}, r => panic!("assertion failed: unexpected {:?}", r), } ) } // This macro is intended to be used with std::io::Error, but other // types with kind() will also work. macro_rules! assert_err_kind { ($expr:expr, $kind:expr) => ( match $expr { Err(e) => assert_eq!(e.kind(), $kind), r => panic!("assertion failed: unexpected {:?}", r), } ) } kamadak-exif-0.5.5/src/util.rs000064400000000000000000000142051046102023000142710ustar 00000000000000// // Copyright (c) 2016 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. // use std::io; use std::io::Read as _; use crate::error::Error; const ASCII_0: u8 = 0x30; const ASCII_9: u8 = 0x39; pub fn read8(reader: &mut R) -> Result where R: io::Read { let mut buf = [0u8; 1]; reader.read_exact(&mut buf).and(Ok(buf[0])) } pub fn read16(reader: &mut R) -> Result where R: io::Read { let mut buf = [0u8; 2]; reader.read_exact(&mut buf)?; Ok(u16::from_be_bytes(buf)) } pub fn read64(reader: &mut R) -> Result where R: io::Read { let mut buf = [0u8; 8]; reader.read_exact(&mut buf)?; Ok(u64::from_be_bytes(buf)) } pub trait BufReadExt { fn discard_exact(&mut self, len: usize) -> io::Result<()>; fn is_eof(&mut self) -> io::Result; } impl BufReadExt for T where T: io::BufRead { fn discard_exact(&mut self, mut len: usize) -> io::Result<()> { while len > 0 { let consume_len = match self.fill_buf() { Ok(buf) if buf.is_empty() => return Err(io::Error::new( io::ErrorKind::UnexpectedEof, "unexpected EOF")), Ok(buf) => buf.len().min(len), Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, Err(e) => return Err(e), }; self.consume(consume_len); len -= consume_len; } Ok(()) } fn is_eof(&mut self) -> io::Result { loop { match self.fill_buf() { Ok(buf) => return Ok(buf.is_empty()), Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, Err(e) => return Err(e), } } } } pub trait ReadExt { fn read_exact_len(&mut self, buf: &mut Vec, len: usize) -> io::Result<()>; } impl ReadExt for T where T: io::Read { fn read_exact_len(&mut self, buf: &mut Vec, len: usize) -> io::Result<()> { // Using `vec![0; len]` and `read_exact` is more efficient but // less robust against broken files; a small file can easily // trigger OOM by a huge length value without actual data. // When the fallible allocation feature is stabilized, // we could revisit this. if self.take(len as u64).read_to_end(buf)? != len { return Err(io::Error::new( io::ErrorKind::UnexpectedEof, "unexpected EOF")); } Ok(()) } } // This function must not be called with more than 4 bytes. pub fn atou16(bytes: &[u8]) -> Result { debug_assert!(bytes.len() <= 4); if bytes.len() == 0 { return Err(Error::InvalidFormat("Not a number")); } let mut n = 0; for &c in bytes { if c < ASCII_0 || ASCII_9 < c { return Err(Error::InvalidFormat("Not a number")); } n = n * 10 + (c - ASCII_0) as u16; } Ok(n) } pub fn ctou32(c: u8) -> Result { if c < ASCII_0 || ASCII_9 < c { return Err(Error::InvalidFormat("Not a number")); } Ok((c - ASCII_0) as u32) } #[cfg(test)] mod tests { use std::io::ErrorKind; use std::io::Read; use super::*; #[test] fn discard_exact() { let mut buf = b"abc".as_ref(); buf.discard_exact(1).unwrap(); assert_eq!(buf, b"bc"); buf.discard_exact(2).unwrap(); assert_eq!(buf, b""); buf.discard_exact(1).unwrap_err(); } #[test] fn read8_len() { let data = []; assert_err_kind!(read8(&mut &data[..]), ErrorKind::UnexpectedEof); let data = [0x01]; assert_ok!(read8(&mut &data[..]), 0x01); let data = [0x01, 0x02]; let mut reader = &data[..]; let mut buf = Vec::new(); assert_ok!(read8(&mut reader), 0x01); assert_ok!(reader.read_to_end(&mut buf), 1); assert_eq!(buf, [0x02]); } #[test] fn read16_len() { let data = []; assert_err_kind!(read16(&mut &data[..]), ErrorKind::UnexpectedEof); let data = [0x01]; assert_err_kind!(read16(&mut &data[..]), ErrorKind::UnexpectedEof); let data = [0x01, 0x02]; assert_ok!(read16(&mut &data[..]), 0x0102); let data = [0x01, 0x02, 0x03]; let mut reader = &data[..]; let mut buf = Vec::new(); assert_ok!(read16(&mut reader), 0x0102); assert_ok!(reader.read_to_end(&mut buf), 1); assert_eq!(buf, [0x03]); } #[test] fn atou16_misc() { assert_ok!(atou16(b"0"), 0); assert_ok!(atou16(b"0010"), 10); assert_ok!(atou16(b"9999"), 9999); assert_err_pat!(atou16(b""), Error::InvalidFormat(_)); assert_err_pat!(atou16(b"/"), Error::InvalidFormat(_)); assert_err_pat!(atou16(b":"), Error::InvalidFormat(_)); assert_err_pat!(atou16(b"-1"), Error::InvalidFormat(_)); } } kamadak-exif-0.5.5/src/value.rs000064400000000000000000001037171046102023000144370ustar 00000000000000// // Copyright (c) 2016 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. // use std::fmt; use std::fmt::Write as _; use crate::endian::Endian; use crate::error::Error; /// A type and values of a TIFF/Exif field. #[derive(Clone)] pub enum Value { /// Vector of 8-bit unsigned integers. Byte(Vec), /// Vector of slices of 8-bit bytes containing 7-bit ASCII characters. /// The trailing null characters are not included. Note that /// the 8th bits may present if a non-conforming data is given. Ascii(Vec>), /// Vector of 16-bit unsigned integers. Short(Vec), /// Vector of 32-bit unsigned integers. Long(Vec), /// Vector of unsigned rationals. /// An unsigned rational number is a pair of 32-bit unsigned integers. Rational(Vec), /// Vector of 8-bit signed integers. Unused in the Exif specification. SByte(Vec), /// Slice of 8-bit bytes. /// /// The second member keeps the offset of the value in the Exif data. /// The interpretation of the value does not generally depend on /// the location, but if it does, the offset information helps. /// When encoding Exif, it is ignored. Undefined(Vec, u32), /// Vector of 16-bit signed integers. Unused in the Exif specification. SShort(Vec), /// Vector of 32-bit signed integers. SLong(Vec), /// Vector of signed rationals. /// A signed rational number is a pair of 32-bit signed integers. SRational(Vec), /// Vector of 32-bit (single precision) floating-point numbers. /// Unused in the Exif specification. Float(Vec), /// Vector of 64-bit (double precision) floating-point numbers. /// Unused in the Exif specification. Double(Vec), /// The type is unknown to this implementation. /// The associated values are the type, the count, and the /// offset of the "Value Offset" element. Unknown(u16, u32, u32), } impl Value { /// Returns an object that implements `std::fmt::Display` for /// printing a value in a tag-specific format. /// The tag of the value is specified as the argument. /// /// If you want to display with the unit, use `Field::display_value`. /// /// # Examples /// /// ``` /// use exif::{Value, Tag}; /// let val = Value::Undefined(b"0231".to_vec(), 0); /// assert_eq!(val.display_as(Tag::ExifVersion).to_string(), "2.31"); /// let val = Value::Short(vec![2]); /// assert_eq!(val.display_as(Tag::ResolutionUnit).to_string(), "inch"); /// ``` #[inline] pub fn display_as(&self, tag: crate::tag::Tag) -> Display { crate::tag::display_value_as(self, tag) } /// Returns the value as a slice if the type is BYTE. #[inline] pub(crate) fn byte(&self) -> Option<&[u8]> { match *self { Value::Byte(ref v) => Some(v), _ => None, } } /// Returns the value as `AsciiValues` if the type is ASCII. #[inline] pub(crate) fn ascii(&self) -> Option { match *self { Value::Ascii(ref v) => Some(AsciiValues(v)), _ => None, } } /// Returns the value as a slice if the type is RATIONAL. #[inline] pub(crate) fn rational(&self) -> Option<&[Rational]> { match *self { Value::Rational(ref v) => Some(v), _ => None, } } /// Returns the value as a slice if the type is UNDEFINED. #[inline] pub(crate) fn undefined(&self) -> Option<&[u8]> { match *self { Value::Undefined(ref v, _) => Some(v), _ => None, } } /// Returns `UIntValue` if the value type is unsigned integer (BYTE, /// SHORT, or LONG). Otherwise `exif::Error` is returned. /// /// The integer(s) can be obtained by `get(&self, index: usize)` method /// on `UIntValue`, which returns `Option`. /// `None` is returned if the index is out of bounds. /// /// # Examples /// /// ``` /// # use exif::Value; /// # fn main() -> std::result::Result<(), Box> { /// let v = Value::Byte(vec![1u8, 2]); /// assert_eq!(v.as_uint()?.get(0), Some(1u32)); /// assert_eq!(v.as_uint()?.get(2), None); /// let v = Value::SLong(vec![1, 2]); /// assert!(v.as_uint().is_err()); /// # Ok(()) } /// ``` #[inline] pub fn as_uint(&self) -> Result<&UIntValue, Error> { UIntValue::ref_from(self) } /// Returns the unsigned integer at the given position. /// None is returned if the value type is not unsigned integer /// (BYTE, SHORT, or LONG) or the position is out of bounds. pub fn get_uint(&self, index: usize) -> Option { match *self { Value::Byte(ref v) if v.len() > index => Some(v[index] as u32), Value::Short(ref v) if v.len() > index => Some(v[index] as u32), Value::Long(ref v) if v.len() > index => Some(v[index]), _ => None, } } /// Returns an iterator over the unsigned integers (BYTE, SHORT, or LONG). /// The iterator yields `u32` regardless of the underlying integer size. /// The returned iterator implements `Iterator` and `ExactSizeIterator` /// traits. /// `None` is returned if the value is not an unsigned integer type. #[inline] pub fn iter_uint(&self) -> Option { match *self { Value::Byte(ref v) => Some(UIntIter { iter: Box::new(v.iter().map(|&x| x as u32)) }), Value::Short(ref v) => Some(UIntIter { iter: Box::new(v.iter().map(|&x| x as u32)) }), Value::Long(ref v) => Some(UIntIter { iter: Box::new(v.iter().map(|&x| x)) }), _ => None, } } } pub struct AsciiValues<'a>(&'a [Vec]); impl<'a> AsciiValues<'a> { pub fn first(&self) -> Option<&'a [u8]> { self.0.first().map(|x| &x[..]) } } #[derive(Debug)] #[repr(transparent)] pub struct UIntValue(Value); impl UIntValue { #[inline] fn ref_from(v: &Value) -> Result<&Self, Error> { match *v { Value::Byte(_) | Value::Short(_) | Value::Long(_) => Ok(unsafe { &*(v as *const Value as *const Self) }), _ => Err(Error::UnexpectedValue("Not unsigned integer")), } } #[inline] pub fn get(&self, index: usize) -> Option { match self.0 { Value::Byte(ref v) => v.get(index).map(|&x| x.into()), Value::Short(ref v) => v.get(index).map(|&x| x.into()), Value::Long(ref v) => v.get(index).map(|&x| x), _ => panic!(), } } } // A struct that wraps std::slice::Iter<'a, u8/u16/u32>. pub struct UIntIter<'a> { iter: Box + 'a> } impl<'a> Iterator for UIntIter<'a> { type Item = u32; #[inline] fn next(&mut self) -> Option { self.iter.next() } #[inline] fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } impl<'a> ExactSizeIterator for UIntIter<'a> {} /// Helper struct for printing a value in a tag-specific format. #[derive(Copy, Clone)] pub struct Display<'a> { pub fmt: fn(&mut dyn fmt::Write, &Value) -> fmt::Result, pub value: &'a Value, } impl<'a> fmt::Display for Display<'a> { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { (self.fmt)(f, self.value) } } impl fmt::Debug for Value { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Byte(v) => f.debug_tuple("Byte").field(v).finish(), Self::Ascii(v) => f.debug_tuple("Ascii") .field(&IterDebugAdapter( || v.iter().map(|x| AsciiDebugAdapter(x)))).finish(), Self::Short(v) => f.debug_tuple("Short").field(v).finish(), Self::Long(v) => f.debug_tuple("Long").field(v).finish(), Self::Rational(v) => f.debug_tuple("Rational").field(v).finish(), Self::SByte(v) => f.debug_tuple("SByte").field(v).finish(), Self::Undefined(v, o) => f.debug_tuple("Undefined") .field(&HexDebugAdapter(v)) .field(&format_args!("ofs={:#x}", o)).finish(), Self::SShort(v) => f.debug_tuple("SShort").field(v).finish(), Self::SLong(v) => f.debug_tuple("SLong").field(v).finish(), Self::SRational(v) => f.debug_tuple("SRational").field(v).finish(), Self::Float(v) => f.debug_tuple("Float").field(v).finish(), Self::Double(v) => f.debug_tuple("Double").field(v).finish(), Self::Unknown(t, c, oo) => f.debug_tuple("Unknown") .field(&format_args!("typ={}", t)) .field(&format_args!("cnt={}", c)) .field(&format_args!("ofs={:#x}", oo)).finish(), } } } struct IterDebugAdapter(F); impl fmt::Debug for IterDebugAdapter where F: Fn() -> T, T: Iterator, I: fmt::Debug { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_list().entries(self.0()).finish() } } struct AsciiDebugAdapter<'a>(&'a [u8]); impl<'a> fmt::Debug for AsciiDebugAdapter<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_char('"')?; self.0.iter().try_for_each(|&c| match c { b'\\' | b'"' => write!(f, "\\{}", c as char), 0x20..=0x7e => f.write_char(c as char), _ => write!(f, "\\x{:02x}", c), })?; f.write_char('"') } } struct HexDebugAdapter<'a>(&'a [u8]); impl<'a> fmt::Debug for HexDebugAdapter<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("0x")?; self.0.iter().try_for_each(|x| write!(f, "{:02x}", x)) } } // Static default values. pub enum DefaultValue { None, Byte(&'static [u8]), Ascii(&'static [&'static [u8]]), Short(&'static [u16]), Rational(&'static [(u32, u32)]), Undefined(&'static [u8]), // Depends on other tags, JPEG markers, etc. ContextDependent, // Unspecified in the Exif standard. Unspecified, } impl From<&DefaultValue> for Option { fn from(defval: &DefaultValue) -> Option { match *defval { DefaultValue::None => None, DefaultValue::Byte(s) => Some(Value::Byte(s.to_vec())), DefaultValue::Ascii(s) => Some(Value::Ascii( s.iter().map(|&x| x.to_vec()).collect())), DefaultValue::Short(s) => Some(Value::Short(s.to_vec())), DefaultValue::Rational(s) => Some(Value::Rational( s.iter().map(|&x| x.into()).collect())), DefaultValue::Undefined(s) => Some(Value::Undefined( s.to_vec(), 0)), DefaultValue::ContextDependent => None, DefaultValue::Unspecified => None, } } } /// An unsigned rational number, which is a pair of 32-bit unsigned integers. #[derive(Copy, Clone)] pub struct Rational { pub num: u32, pub denom: u32 } impl Rational { /// Converts the value to an f32. #[inline] pub fn to_f32(&self) -> f32 { self.to_f64() as f32 } /// Converts the value to an f64. #[inline] pub fn to_f64(&self) -> f64 { self.num as f64 / self.denom as f64 } } impl From<(u32, u32)> for Rational { fn from(t: (u32, u32)) -> Rational { Rational { num: t.0, denom: t.1 } } } impl fmt::Debug for Rational { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Rational({}/{})", self.num, self.denom) } } impl fmt::Display for Rational { /// Formatting parameters other than width are not supported. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let buf = fmt_rational_sub(f, self.num, self.denom); f.pad_integral(true, "", &buf) } } // This implementation has been deprecated. Use Rational::to_f64 instead. impl From for f64 { #[inline] fn from(r: Rational) -> f64 { r.to_f64() } } // This implementation has been deprecated. Use Rational::to_f32 instead. impl From for f32 { #[inline] fn from(r: Rational) -> f32 { r.to_f32() } } /// A signed rational number, which is a pair of 32-bit signed integers. #[derive(Copy, Clone)] pub struct SRational { pub num: i32, pub denom: i32 } impl SRational { /// Converts the value to an f32. #[inline] pub fn to_f32(&self) -> f32 { self.to_f64() as f32 } /// Converts the value to an f64. #[inline] pub fn to_f64(&self) -> f64 { self.num as f64 / self.denom as f64 } } impl From<(i32, i32)> for SRational { fn from(t: (i32, i32)) -> SRational { SRational { num: t.0, denom: t.1 } } } impl fmt::Debug for SRational { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "SRational({}/{})", self.num, self.denom) } } impl fmt::Display for SRational { /// Formatting parameters other than width are not supported. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let buf = fmt_rational_sub( f, self.num.wrapping_abs() as u32, self.denom); f.pad_integral(self.num >= 0, "", &buf) } } // This implementation has been deprecated. Use SRational::to_f64 instead. impl From for f64 { #[inline] fn from(r: SRational) -> f64 { r.to_f64() } } // This implementation has been deprecated. Use SRational::to_f32 instead. impl From for f32 { #[inline] fn from(r: SRational) -> f32 { r.to_f32() } } // Only u32 or i32 are expected for T. fn fmt_rational_sub(f: &mut fmt::Formatter, num: u32, denom: T) -> String where T: fmt::Display { // The API to get the alignment is not yet stable as of Rust 1.16, // so it is not fully supported. match (f.sign_plus(), f.precision(), f.sign_aware_zero_pad()) { (true, Some(prec), true) => format!("{}/{:+0w$}", num, denom, w = prec), (true, Some(prec), false) => format!("{}/{:+w$}", num, denom, w = prec), (true, None, _) => format!("{}/{:+}", num, denom), (false, Some(prec), true) => format!("{}/{:0w$}", num, denom, w = prec), (false, Some(prec), false) => format!("{}/{:w$}", num, denom, w = prec), (false, None, _) => format!("{}/{}", num, denom), } } type Parser = fn(&[u8], usize, usize) -> Value; // Return the length of a single value and the parser of the type. pub fn get_type_info(typecode: u16) -> (usize, Parser) where E: Endian { match typecode { 1 => (1, parse_byte), 2 => (1, parse_ascii), 3 => (2, parse_short::), 4 => (4, parse_long::), 5 => (8, parse_rational::), 6 => (1, parse_sbyte), 7 => (1, parse_undefined), 8 => (2, parse_sshort::), 9 => (4, parse_slong::), 10 => (8, parse_srational::), 11 => (4, parse_float::), 12 => (8, parse_double::), _ => (0, parse_unknown), } } fn parse_byte(data: &[u8], offset: usize, count: usize) -> Value { Value::Byte(data[offset .. offset + count].to_vec()) } fn parse_ascii(data: &[u8], offset: usize, count: usize) -> Value { // Any ASCII field can contain multiple strings [TIFF6 Image File // Directory]. let iter = (&data[offset .. offset + count]).split(|&b| b == b'\0'); let mut v: Vec> = iter.map(|x| x.to_vec()).collect(); if v.last().map_or(false, |x| x.len() == 0) { v.pop(); } Value::Ascii(v) } fn parse_short(data: &[u8], offset: usize, count: usize) -> Value where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(E::loadu16(data, offset + i * 2)); } Value::Short(val) } fn parse_long(data: &[u8], offset: usize, count: usize) -> Value where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(E::loadu32(data, offset + i * 4)); } Value::Long(val) } fn parse_rational(data: &[u8], offset: usize, count: usize) -> Value where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(Rational { num: E::loadu32(data, offset + i * 8), denom: E::loadu32(data, offset + i * 8 + 4), }); } Value::Rational(val) } fn parse_sbyte(data: &[u8], offset: usize, count: usize) -> Value { let bytes = data[offset .. offset + count].iter() .map(|x| *x as i8).collect(); Value::SByte(bytes) } fn parse_undefined(data: &[u8], offset: usize, count: usize) -> Value { Value::Undefined(data[offset .. offset + count].to_vec(), offset as u32) } fn parse_sshort(data: &[u8], offset: usize, count: usize) -> Value where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(E::loadu16(data, offset + i * 2) as i16); } Value::SShort(val) } fn parse_slong(data: &[u8], offset: usize, count: usize) -> Value where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(E::loadu32(data, offset + i * 4) as i32); } Value::SLong(val) } fn parse_srational(data: &[u8], offset: usize, count: usize) -> Value where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(SRational { num: E::loadu32(data, offset + i * 8) as i32, denom: E::loadu32(data, offset + i * 8 + 4) as i32, }); } Value::SRational(val) } // TIFF and Rust use IEEE 754 format, so no conversion is required. fn parse_float(data: &[u8], offset: usize, count: usize) -> Value where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(f32::from_bits(E::loadu32(data, offset + i * 4))); } Value::Float(val) } // TIFF and Rust use IEEE 754 format, so no conversion is required. fn parse_double(data: &[u8], offset: usize, count: usize) -> Value where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(f64::from_bits(E::loadu64(data, offset + i * 8))); } Value::Double(val) } // This is a dummy function and will never be called. #[allow(unused_variables)] fn parse_unknown(data: &[u8], offset: usize, count: usize) -> Value { unreachable!() } #[cfg(test)] mod tests { use crate::endian::BigEndian; use super::*; #[test] fn byte() { let sets: &[(&[u8], &[u8])] = &[ (b"x", b""), (b"x\xbe\xad", b"\xbe\xad"), ]; let (unitlen, parser) = get_type_info::(1); for &(data, ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Byte(v) => assert_eq!(v, ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn ascii() { let sets: &[(&[u8], Vec<&[u8]>)] = &[ (b"x", vec![]), // malformed (b"x\0", vec![b""]), (b"x\0\0", vec![b"", b""]), (b"xA", vec![b"A"]), // malformed (b"xA\0", vec![b"A"]), (b"xA\0B", vec![b"A", b"B"]), // malformed (b"xA\0B\0", vec![b"A", b"B"]), (b"xA\0\xbe\0", vec![b"A", b"\xbe"]), // not ASCII ]; let (unitlen, parser) = get_type_info::(2); for &(data, ref ans) in sets { match parser(data, 1, (data.len() - 1) / unitlen) { Value::Ascii(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn short() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x01\x02\x03\x04", vec![0x0102, 0x0304]), ]; let (unitlen, parser) = get_type_info::(3); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Short(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn long() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x01\x02\x03\x04\x05\x06\x07\x08", vec![0x01020304, 0x05060708]), ]; let (unitlen, parser) = get_type_info::(4); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Long(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn rational() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\xa1\x02\x03\x04\x05\x06\x07\x08\ \x09\x0a\x0b\x0c\xbd\x0e\x0f\x10", vec![(0xa1020304, 0x05060708).into(), (0x090a0b0c, 0xbd0e0f10).into()]), ]; let (unitlen, parser) = get_type_info::(5); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Rational(v) => { assert_eq!(v.len(), ans.len()); for (x, y) in v.iter().zip(ans.iter()) { assert!(x.num == y.num && x.denom == y.denom); } }, v => panic!("wrong variant {:?}", v), } } } #[test] fn sbyte() { let sets: &[(&[u8], &[i8])] = &[ (b"x", &[]), (b"x\xbe\x7d", &[-0x42, 0x7d]), ]; let (unitlen, parser) = get_type_info::(6); for &(data, ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::SByte(v) => assert_eq!(v, ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn undefined() { let sets: &[(&[u8], &[u8])] = &[ (b"x", b""), (b"x\xbe\xad", b"\xbe\xad"), ]; let (unitlen, parser) = get_type_info::(7); for &(data, ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Undefined(v, o) => { assert_eq!(v, ans); assert_eq!(o, 1); }, v => panic!("wrong variant {:?}", v), } } } #[test] fn sshort() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x01\x02\xf3\x04", vec![0x0102, -0x0cfc]), ]; let (unitlen, parser) = get_type_info::(8); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::SShort(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn slong() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x01\x02\x03\x04\x85\x06\x07\x08", vec![0x01020304, -0x7af9f8f8]), ]; let (unitlen, parser) = get_type_info::(9); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::SLong(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn srational() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\xa1\x02\x03\x04\x05\x06\x07\x08\ \x09\x0a\x0b\x0c\xbd\x0e\x0f\x10", vec![(-0x5efdfcfc, 0x05060708).into(), (0x090a0b0c, -0x42f1f0f0).into()]), ]; let (unitlen, parser) = get_type_info::(10); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::SRational(v) => { assert_eq!(v.len(), ans.len()); for (x, y) in v.iter().zip(ans.iter()) { assert!(x.num == y.num && x.denom == y.denom); } }, v => panic!("wrong variant {:?}", v), } } } #[test] fn float() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x7f\x7f\xff\xff\x80\x80\x00\x00\x40\x00\x00\x00", vec![std::f32::MAX, -std::f32::MIN_POSITIVE, 2.0]), ]; let (unitlen, parser) = get_type_info::(11); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Float(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn double() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x7f\xef\xff\xff\xff\xff\xff\xff\ \x80\x10\x00\x00\x00\x00\x00\x00\ \x40\x00\x00\x00\x00\x00\x00\x00", vec![std::f64::MAX, -std::f64::MIN_POSITIVE, 2.0]), ]; let (unitlen, parser) = get_type_info::(12); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Double(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } // These functions are never called in a way that an out-of-range access // could happen, so this test is hypothetical but just for safety. #[test] #[should_panic(expected = "index 5 out of range for slice of length 4")] fn short_oor() { parse_short::(b"\x01\x02\x03\x04", 1, 2); } #[test] fn unknown() { let (unitlen, _parser) = get_type_info::(0xffff); assert_eq!(unitlen, 0); } #[test] fn as_uint() { let v = Value::Byte(vec![1, 2]); assert_eq!(v.as_uint().unwrap().get(0), Some(1)); assert_eq!(v.as_uint().unwrap().get(1), Some(2)); assert_eq!(v.as_uint().unwrap().get(2), None); let v = Value::Short(vec![1, 2]); assert_eq!(v.as_uint().unwrap().get(0), Some(1)); assert_eq!(v.as_uint().unwrap().get(1), Some(2)); assert_eq!(v.as_uint().unwrap().get(2), None); let v = Value::Long(vec![1, 2]); assert_eq!(v.as_uint().unwrap().get(0), Some(1)); assert_eq!(v.as_uint().unwrap().get(1), Some(2)); assert_eq!(v.as_uint().unwrap().get(2), None); let v = Value::SLong(vec![1, 2]); assert_err_pat!(v.as_uint(), Error::UnexpectedValue(_)); } #[test] fn get_uint() { let v = Value::Byte(vec![1, 2]); assert_eq!(v.get_uint(0), Some(1)); assert_eq!(v.get_uint(1), Some(2)); assert_eq!(v.get_uint(2), None); let v = Value::Short(vec![1, 2]); assert_eq!(v.get_uint(0), Some(1)); assert_eq!(v.get_uint(1), Some(2)); assert_eq!(v.get_uint(2), None); let v = Value::Long(vec![1, 2]); assert_eq!(v.get_uint(0), Some(1)); assert_eq!(v.get_uint(1), Some(2)); assert_eq!(v.get_uint(2), None); let v = Value::SLong(vec![1, 2]); assert_eq!(v.get_uint(0), None); assert_eq!(v.get_uint(1), None); assert_eq!(v.get_uint(2), None); } #[test] fn iter_uint() { let vlist = &[ Value::Byte(vec![1, 2]), Value::Short(vec![1, 2]), Value::Long(vec![1, 2]), ]; for v in vlist { let mut it = v.iter_uint().unwrap(); assert_eq!(it.next(), Some(1)); assert_eq!(it.next(), Some(2)); assert_eq!(it.next(), None); } let v = Value::SLong(vec![1, 2]); assert!(v.iter_uint().is_none()); } #[test] fn iter_uint_is_exact_size_iter() { let v = Value::Byte(vec![1, 2, 3]); let mut it = v.iter_uint().unwrap(); assert_eq!(it.len(), 3); assert_eq!(it.next(), Some(1)); assert_eq!(it.len(), 2); } #[test] fn value_fmt_debug() { let v = Value::Byte(b"b\0y".to_vec()); assert_eq!(format!("{:?}", v), "Byte([98, 0, 121])"); let v = Value::Ascii(vec![]); assert_eq!(format!("{:?}", v), "Ascii([])"); let v = Value::Ascii(vec![b"abc\"\\\n\x7f".to_vec(), b"".to_vec()]); assert_eq!(format!("{:?}", v), r#"Ascii(["abc\"\\\x0a\x7f", ""])"#); let v = Value::Short(vec![]); assert_eq!(format!("{:?}", v), "Short([])"); let v = Value::Long(vec![1, 2]); assert_eq!(format!("{:?}", v), "Long([1, 2])"); let v = Value::Rational(vec![(0, 0).into()]); assert_eq!(format!("{:?}", v), "Rational([Rational(0/0)])"); let v = Value::SByte(vec![-3, 4, 5]); assert_eq!(format!("{:?}", v), "SByte([-3, 4, 5])"); let v = Value::Undefined(vec![0, 0xff], 0); assert_eq!(format!("{:?}", v), "Undefined(0x00ff, ofs=0x0)"); let v = Value::SShort(vec![6, -7]); assert_eq!(format!("{:?}", v), "SShort([6, -7])"); let v = Value::SLong(vec![-9]); assert_eq!(format!("{:?}", v), "SLong([-9])"); let v = Value::SRational(vec![(-2, -1).into()]); assert_eq!(format!("{:?}", v), "SRational([SRational(-2/-1)])"); let v = Value::Float(vec![1.5, 0.0]); assert_eq!(format!("{:?}", v), "Float([1.5, 0.0])"); let v = Value::Double(vec![-0.5, 1.0]); assert_eq!(format!("{:?}", v), "Double([-0.5, 1.0])"); let v = Value::Unknown(1, 2, 10); assert_eq!(format!("{:?}", v), "Unknown(typ=1, cnt=2, ofs=0xa)"); } #[test] fn rational_fmt_display() { let r = Rational::from((u32::max_value(), u32::max_value())); assert_eq!(format!("{}", r), "4294967295/4294967295"); let r = Rational::from((10, 20)); assert_eq!(format!("{}", r), "10/20"); assert_eq!(format!("{:11}", r), " 10/20"); assert_eq!(format!("{:3}", r), "10/20"); } #[test] fn srational_fmt_display() { let r = SRational::from((i32::min_value(), i32::min_value())); assert_eq!(format!("{}", r), "-2147483648/-2147483648"); let r = SRational::from((i32::max_value(), i32::max_value())); assert_eq!(format!("{}", r), "2147483647/2147483647"); let r = SRational::from((-10, 20)); assert_eq!(format!("{}", r), "-10/20"); assert_eq!(format!("{:11}", r), " -10/20"); assert_eq!(format!("{:3}", r), "-10/20"); let r = SRational::from((10, -20)); assert_eq!(format!("{}", r), "10/-20"); assert_eq!(format!("{:11}", r), " 10/-20"); assert_eq!(format!("{:3}", r), "10/-20"); let r = SRational::from((-10, -20)); assert_eq!(format!("{}", r), "-10/-20"); assert_eq!(format!("{:11}", r), " -10/-20"); assert_eq!(format!("{:3}", r), "-10/-20"); } #[test] fn ratioanl_f64() { use std::{f64, u32}; assert_eq!(f64::from(Rational::from((1, 2))), 0.5); assert_eq!(f64::from(Rational::from((1, u32::MAX))), 2.3283064370807974e-10); assert_eq!(f64::from(Rational::from((u32::MAX, 1))), u32::MAX as f64); assert_eq!(f64::from(Rational::from((u32::MAX - 1, u32::MAX))), 0.9999999997671694); assert_eq!(f64::from(Rational::from((u32::MAX, u32::MAX - 1))), 1.0000000002328306); assert_eq!(f64::from(Rational::from((1, 0))), f64::INFINITY); assert!(f64::from(Rational::from((0, 0))).is_nan()); assert_eq!(f64::from(SRational::from((1, 2))), 0.5); assert_eq!(f64::from(SRational::from((-1, 2))), -0.5); assert_eq!(f64::from(SRational::from((1, -2))), -0.5); assert_eq!(f64::from(SRational::from((-1, -2))), 0.5); assert_eq!(f64::from(SRational::from((1, 0))), f64::INFINITY); assert_eq!(f64::from(SRational::from((-1, 0))), f64::NEG_INFINITY); } #[test] fn rational_f32() { // If num and demon are converted to f32 before the division, // the precision is lost in this example. assert_eq!(f32::from(Rational::from((1, 16777217))), 5.960464e-8); } } kamadak-exif-0.5.5/src/webp.rs000064400000000000000000000123051046102023000142500ustar 00000000000000// // Copyright (c) 2020 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. // use std::io::{BufRead, ErrorKind}; use crate::endian::{Endian, LittleEndian}; use crate::error::Error; use crate::util::{BufReadExt as _, ReadExt as _}; // Chunk identifiers for RIFF. const FCC_RIFF: [u8; 4] = *b"RIFF"; const FCC_WEBP: [u8; 4] = *b"WEBP"; const FCC_EXIF: [u8; 4] = *b"EXIF"; // Get the contents of the Exif chunk from a WebP file. pub fn get_exif_attr(reader: &mut R) -> Result, Error> where R: BufRead { match get_exif_attr_sub(reader) { Err(Error::Io(ref e)) if e.kind() == ErrorKind::UnexpectedEof => Err(Error::InvalidFormat("Broken WebP file")), r => r, } } fn get_exif_attr_sub(reader: &mut R) -> Result, Error> where R: BufRead { let mut sig = [0; 12]; reader.read_exact(&mut sig)?; if sig[0..4] != FCC_RIFF || sig[8..12] != FCC_WEBP { return Err(Error::InvalidFormat("Not a WebP file")); } let mut file_size = LittleEndian::loadu32(&sig, 4) as usize; file_size = file_size.checked_sub(4) .ok_or(Error::InvalidFormat("Invalid header file size"))?; // Scan the series of chunks. while file_size > 0 { file_size = file_size.checked_sub(8) .ok_or(Error::InvalidFormat("Chunk overflowing parent"))?; let mut cheader = [0; 8]; reader.read_exact(&mut cheader)?; let mut size = LittleEndian::loadu32(&cheader, 4) as usize; file_size = file_size.checked_sub(size) .ok_or(Error::InvalidFormat("Chunk overflowing parent"))?; if cheader[0..4] == FCC_EXIF { let mut payload = Vec::new(); reader.read_exact_len(&mut payload, size)?; return Ok(payload); } if size % 2 != 0 && file_size > 0 { file_size -= 1; size = size.checked_add(1).expect("ex-file_size - size > 0"); } reader.discard_exact(size)?; } Err(Error::NotFound("WebP")) } pub fn is_webp(buf: &[u8]) -> bool { buf.len() >= 12 && buf[0..4] == FCC_RIFF && buf[8..12] == FCC_WEBP } #[cfg(test)] mod tests { use super::*; #[test] fn truncated() { let mut data = b"RIFF\x10\0\0\0WEBPEXIF\x04\0\0\0Exif".to_vec(); assert_eq!(get_exif_attr(&mut &data[..]).unwrap(), b"Exif"); while let Some(_) = data.pop() { get_exif_attr(&mut &data[..]).unwrap_err(); } } #[test] fn no_exif() { let data = b"RIFF\x0c\0\0\0WEBPwhat\0\0\0\0"; assert_err_pat!(get_exif_attr(&mut &data[..]), Error::NotFound(_)); } #[test] fn empty() { let data = b"RIFF\x16\0\0\0WEBPodd_\x01\0\0\0X\0EXIF\0\0\0\0"; assert_ok!(get_exif_attr(&mut &data[..]), b""); } #[test] fn non_empty() { let data = b"RIFF\x1a\0\0\0WEBPeven\x02\0\0\0XYEXIF\x03\0\0\0abcd"; assert_ok!(get_exif_attr(&mut &data[..]), b"abc"); } #[test] fn read_first() { let data = b"RIFF\x18\0\0\0WEBPEXIF\x02\0\0\0abEXIF\x02\0\0\0cd"; assert_ok!(get_exif_attr(&mut &data[..]), b"ab"); } #[test] fn out_of_toplevel_chunk() { let data = b"RIFF\x0e\0\0\0WEBPwhat\x02\0\0\0abEXIF\x02\0\0\0cd"; assert_err_pat!(get_exif_attr(&mut &data[..]), Error::NotFound(_)); } #[test] fn overflowing_parent() { let mut data = b"RIFF\x10\0\0\0WEBPEXIF\x04\0\0\0Exif".to_vec(); assert_eq!(get_exif_attr(&mut &data[..]).unwrap(), b"Exif"); for x in 0x05..=0x0f { data[4] = x; assert_err_pat!(get_exif_attr(&mut &data[..]), Error::InvalidFormat(_)); } data[4] = 0x04; assert_err_pat!(get_exif_attr(&mut &data[..]), Error::NotFound(_)); } #[test] fn odd_at_last_without_padding() { let data = b"RIFF\x17\0\0\0WEBPwhat\0\0\0\0EXIF\x03\0\0\0abc"; assert_ok!(get_exif_attr(&mut &data[..]), b"abc"); } } kamadak-exif-0.5.5/src/writer.rs000064400000000000000000000753521046102023000146420ustar 00000000000000// // 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. // use std::io; use std::io::{Seek, SeekFrom, Write}; use crate::endian::{Endian, BigEndian, LittleEndian}; use crate::error::Error; use crate::tag::{Context, Tag}; use crate::tiff::{Field, In, TIFF_BE_SIG, TIFF_LE_SIG}; use crate::value::Value; /// The `Writer` struct is used to encode and write Exif data. /// /// # Examples /// /// ``` /// # fn main() -> Result<(), Box> { /// use exif::{Field, In, Tag, Value}; /// use exif::experimental::Writer; /// let image_desc = Field { /// tag: Tag::ImageDescription, /// ifd_num: In::PRIMARY, /// value: Value::Ascii(vec![b"Sample".to_vec()]), /// }; /// let mut writer = Writer::new(); /// let mut buf = std::io::Cursor::new(Vec::new()); /// writer.push_field(&image_desc); /// writer.write(&mut buf, false)?; /// static expected: &[u8] = /// b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ /// \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x07\x00\x00\x00\x1a\ /// \x00\x00\x00\x00\ /// Sample\0"; /// assert_eq!(buf.into_inner(), expected); /// # Ok(()) } /// ``` #[derive(Debug)] pub struct Writer<'a> { ifd_list: Vec>, } #[derive(Debug, Default)] struct Ifd<'a> { tiff_fields: Vec<&'a Field>, exif_fields: Vec<&'a Field>, gps_fields: Vec<&'a Field>, interop_fields: Vec<&'a Field>, strips: Option<&'a [&'a [u8]]>, tiles: Option<&'a [&'a [u8]]>, jpeg: Option<&'a [u8]>, } impl<'a> Ifd<'a> { fn is_empty(&self) -> bool { self.tiff_fields.is_empty() && self.exif_fields.is_empty() && self.gps_fields.is_empty() && self.interop_fields.is_empty() && self.strips.is_none() && self.tiles.is_none() && self.jpeg.is_none() } } struct WriterState<'a> { tiff_fields: Vec<&'a Field>, exif_fields: Vec<&'a Field>, gps_fields: Vec<&'a Field>, interop_fields: Vec<&'a Field>, tiff_ifd_offset: u32, exif_ifd_offset: u32, gps_ifd_offset: u32, interop_ifd_offset: u32, } impl<'a> Writer<'a> { /// Constructs an empty `Writer`. pub fn new() -> Writer<'a> { Writer { ifd_list: Vec::new(), } } /// Appends a field to be written. /// /// The fields can be appended in any order. /// Duplicate fields must not be appended. /// /// The following fields are ignored and synthesized when needed: /// ExifIFDPointer, GPSInfoIFDPointer, InteropIFDPointer, /// StripOffsets, StripByteCounts, TileOffsets, TileByteCounts, /// JPEGInterchangeFormat, and JPEGInterchangeFormatLength. pub fn push_field(&mut self, field: &'a Field) { match *field { // Ignore the tags for the internal data structure. Field { tag: Tag::ExifIFDPointer, .. } | Field { tag: Tag::GPSInfoIFDPointer, .. } | Field { tag: Tag::InteropIFDPointer, .. } => {}, // These tags are synthesized from the actual strip/tile data. Field { tag: Tag::StripOffsets, .. } | Field { tag: Tag::StripByteCounts, .. } | Field { tag: Tag::TileOffsets, .. } | Field { tag: Tag::TileByteCounts, .. } => {}, // These tags are synthesized from the actual JPEG thumbnail. Field { tag: Tag::JPEGInterchangeFormat, .. } | Field { tag: Tag::JPEGInterchangeFormatLength, .. } => {}, // Other normal tags. Field { tag: Tag(ctx, _), ifd_num, .. } => { let ifd = self.pick_ifd(ifd_num); match ctx { Context::Tiff => ifd.tiff_fields.push(field), Context::Exif => ifd.exif_fields.push(field), Context::Gps => ifd.gps_fields.push(field), Context::Interop => ifd.interop_fields.push(field), } }, } } /// Sets TIFF strips for the specified IFD. /// If this method is called multiple times, the last one is used. pub fn set_strips(&mut self, strips: &'a [&'a [u8]], ifd_num: In) { self.pick_ifd(ifd_num).strips = Some(strips); } /// Sets TIFF tiles for the specified IFD. /// If this method is called multiple times, the last one is used. pub fn set_tiles(&mut self, tiles: &'a [&'a [u8]], ifd_num: In) { self.pick_ifd(ifd_num).tiles = Some(tiles); } /// Sets JPEG data for the specified IFD. /// If this method is called multiple times, the last one is used. pub fn set_jpeg(&mut self, jpeg: &'a [u8], ifd_num: In) { self.pick_ifd(ifd_num).jpeg = Some(jpeg); } /// Encodes Exif data and writes it into `w`. /// /// The write position of `w` must be set to zero before calling /// this method. pub fn write(&mut self, w: &mut W, little_endian: bool) -> Result<(), Error> where W: Write + Seek { // TIFF signature and the offset of the 0th IFD. if little_endian { w.write_all(&TIFF_LE_SIG)?; LittleEndian::writeu32(w, 8)?; } else { w.write_all(&TIFF_BE_SIG)?; BigEndian::writeu32(w, 8)?; } // There must be at least 1 IFD in a TIFF file [TIFF6, Section 2, // Image File Directory]. if self.ifd_list.is_empty() { return Err(Error::InvalidFormat("At least one IFD must exist")); } let mut ifd_num_ck = Some(0); let mut next_ifd_offset_offset = 4; for ifd in &self.ifd_list { // Each IFD must have at least one entry [TIFF6, Section 2, // Image File Directory]. if ifd.is_empty() { return Err(Error::InvalidFormat("IFD must not be empty")); } let ifd_num = ifd_num_ck.ok_or(Error::InvalidFormat("Too many IFDs"))?; if ifd_num > 0 { let next_ifd_offset = pad_and_get_offset(w)?; let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(next_ifd_offset_offset as u64))?; match little_endian { false => BigEndian::writeu32(w, next_ifd_offset)?, true => LittleEndian::writeu32(w, next_ifd_offset)?, } w.seek(SeekFrom::Start(origpos))?; } next_ifd_offset_offset = synthesize_fields(w, ifd, In(ifd_num), little_endian)?; ifd_num_ck = ifd_num.checked_add(1); } w.flush()?; Ok(()) } fn pick_ifd(&mut self, ifd_num: In) -> &mut Ifd<'a> { let ifd_num = ifd_num.index() as usize; if self.ifd_list.len() <= ifd_num { self.ifd_list.resize_with(ifd_num + 1, Default::default); } &mut self.ifd_list[ifd_num] } } // Synthesizes special fields, writes an image, and returns the offset // of the next IFD offset. fn synthesize_fields(w: &mut W, ifd: &Ifd, ifd_num: In, little_endian: bool) -> Result where W: Write + Seek { let exif_in_tiff; let gps_in_tiff; let interop_in_exif; let strip_offsets; let strip_byte_counts; let tile_offsets; let tile_byte_counts; let jpeg_offset; let jpeg_length; let mut ws = WriterState { tiff_fields: ifd.tiff_fields.clone(), exif_fields: ifd.exif_fields.clone(), gps_fields: ifd.gps_fields.clone(), interop_fields: ifd.interop_fields.clone(), tiff_ifd_offset: 0, exif_ifd_offset: 0, gps_ifd_offset: 0, interop_ifd_offset: 0, }; if let Some(strips) = ifd.strips { strip_offsets = Field { tag: Tag::StripOffsets, ifd_num: ifd_num, value: Value::Long(vec![0; strips.len()]), }; ws.tiff_fields.push(&strip_offsets); strip_byte_counts = Field { tag: Tag::StripByteCounts, ifd_num: ifd_num, value: Value::Long( strips.iter().map(|s| s.len() as u32).collect()), }; ws.tiff_fields.push(&strip_byte_counts); } if let Some(tiles) = ifd.tiles { tile_offsets = Field { tag: Tag::TileOffsets, ifd_num: ifd_num, value: Value::Long(vec![0; tiles.len()]), }; ws.tiff_fields.push(&tile_offsets); tile_byte_counts = Field { tag: Tag::TileByteCounts, ifd_num: ifd_num, value: Value::Long( tiles.iter().map(|s| s.len() as u32).collect()), }; ws.tiff_fields.push(&tile_byte_counts); } if let Some(jpeg) = ifd.jpeg { jpeg_offset = Field { tag: Tag::JPEGInterchangeFormat, ifd_num: ifd_num, value: Value::Long(vec![0]), }; ws.tiff_fields.push(&jpeg_offset); jpeg_length = Field { tag: Tag::JPEGInterchangeFormatLength, ifd_num: ifd_num, value: Value::Long(vec![jpeg.len() as u32]), }; ws.tiff_fields.push(&jpeg_length); } let interop_fields_len = ws.interop_fields.len(); let gps_fields_len = ws.gps_fields.len(); let exif_fields_len = ws.exif_fields.len() + match interop_fields_len { 0 => 0, _ => 1 }; let tiff_fields_len = ws.tiff_fields.len() + match gps_fields_len { 0 => 0, _ => 1 } + match exif_fields_len { 0 => 0, _ => 1 }; assert_ne!(tiff_fields_len, 0); ws.tiff_ifd_offset = reserve_ifd(w, tiff_fields_len)?; if exif_fields_len > 0 { ws.exif_ifd_offset = reserve_ifd(w, exif_fields_len)?; exif_in_tiff = Field { tag: Tag::ExifIFDPointer, ifd_num: ifd_num, value: Value::Long(vec![ws.exif_ifd_offset]), }; ws.tiff_fields.push(&exif_in_tiff); } if gps_fields_len > 0 { ws.gps_ifd_offset = reserve_ifd(w, gps_fields_len)?; gps_in_tiff = Field { tag: Tag::GPSInfoIFDPointer, ifd_num: ifd_num, value: Value::Long(vec![ws.gps_ifd_offset]), }; ws.tiff_fields.push(&gps_in_tiff); } if interop_fields_len > 0 { ws.interop_ifd_offset = reserve_ifd(w, interop_fields_len)?; interop_in_exif = Field { tag: Tag::InteropIFDPointer, ifd_num: ifd_num, value: Value::Long(vec![ws.interop_ifd_offset]), }; ws.exif_fields.push(&interop_in_exif); } ws.tiff_fields.sort_by_key(|f| f.tag.number()); ws.exif_fields.sort_by_key(|f| f.tag.number()); ws.gps_fields.sort_by_key(|f| f.tag.number()); ws.interop_fields.sort_by_key(|f| f.tag.number()); match little_endian { false => write_image::<_, BigEndian>(w, &ws, ifd), true => write_image::<_, LittleEndian>(w, &ws, ifd), } } // Writes an image and returns the offset of the next IFD offset. fn write_image(w: &mut W, ws: &WriterState, ifd: &Ifd) -> Result where W: Write + Seek, E: Endian { let (next_ifd_offset_offset, strip_offsets_offset, tile_offsets_offset, jpeg_offset) = write_ifd_and_fields::<_, E>( w, &ws.tiff_fields, ws.tiff_ifd_offset)?; if ws.exif_fields.len() > 0 { write_ifd_and_fields::<_, E>( w, &ws.exif_fields, ws.exif_ifd_offset)?; } if ws.gps_fields.len() > 0 { write_ifd_and_fields::<_, E>( w, &ws.gps_fields, ws.gps_ifd_offset)?; } if ws.interop_fields.len() > 0 { write_ifd_and_fields::<_, E>( w, &ws.interop_fields, ws.interop_ifd_offset)?; } if let Some(strips) = ifd.strips { let mut strip_offsets = Vec::new(); for strip in strips { strip_offsets.push(get_offset(w)?); w.write_all(strip)?; } let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(strip_offsets_offset as u64))?; for ofs in strip_offsets { E::writeu32(w, ofs)?; } w.seek(SeekFrom::Start(origpos))?; } if let Some(tiles) = ifd.tiles { let mut tile_offsets = Vec::new(); for tile in tiles { tile_offsets.push(get_offset(w)?); w.write_all(tile)?; } let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(tile_offsets_offset as u64))?; for ofs in tile_offsets { E::writeu32(w, ofs)?; } w.seek(SeekFrom::Start(origpos))?; } if let Some(jpeg) = ifd.jpeg { let offset = get_offset(w)?; w.write_all(jpeg)?; let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(jpeg_offset as u64))?; E::writeu32(w, offset)?; w.seek(SeekFrom::Start(origpos))?; } Ok(next_ifd_offset_offset) } // Advances the write position to make a space for a new IFD and // returns the offset of the IFD. fn reserve_ifd(w: &mut W, count: usize) -> Result where W: Write + Seek { let ifdpos = get_offset(w)?; assert!(ifdpos % 2 == 0); // The number of entries (2) + array of entries (12 * n) + // the next IFD pointer (4). w.seek(SeekFrom::Current(2 + count as i64 * 12 + 4))?; Ok(ifdpos) } // Writes an IFD and its fields, and // returns the offsets of the next IFD offset, StripOffsets value, // TileOffsets value, and JPEGInterchangeFormat value. fn write_ifd_and_fields( w: &mut W, fields: &Vec<&Field>, ifd_offset: u32) -> Result<(u32, u32, u32, u32), Error> where W: Write + Seek, E: Endian { let mut strip_offsets_offset = 0; let mut tile_offsets_offset = 0; let mut jpeg_offset = 0; let mut ifd = Vec::new(); // Write the number of entries. E::writeu16(&mut ifd, fields.len() as u16)?; // Write the fields. for f in fields { let (typ, cnt, mut valbuf) = compose_value::(&f.value)?; if cnt as u32 as usize != cnt { return Err(Error::TooBig("Too long array")); } E::writeu16(&mut ifd, f.tag.number())?; E::writeu16(&mut ifd, typ)?; E::writeu32(&mut ifd, cnt as u32)?; // Embed the value itself into the offset, or // encode as an offset and the value. if valbuf.len() <= 4 { valbuf.resize(4, 0); ifd.write_all(&valbuf)?; } else { // The value must begin on a word boundary. [TIFF6, Section 2: // TIFF Structure, Image File Directory, IFD Entry, p. 15] let valofs = pad_and_get_offset(w)?; E::writeu32(&mut ifd, valofs)?; w.write_all(&valbuf)?; } if f.tag == Tag::StripOffsets { strip_offsets_offset = match valbuf.len() { 0..=4 => ifd_offset + ifd.len() as u32 - 4, _ => get_offset(w)? - valbuf.len() as u32, }; } if f.tag == Tag::TileOffsets { tile_offsets_offset = match valbuf.len() { 0..=4 => ifd_offset + ifd.len() as u32 - 4, _ => get_offset(w)? - valbuf.len() as u32, }; } if f.tag == Tag::JPEGInterchangeFormat { jpeg_offset = ifd_offset + ifd.len() as u32 - 4; } } // Write the next IFD pointer. let next_ifd_offset_offset = ifd_offset + ifd.len() as u32; E::writeu32(&mut ifd, 0)?; // Write the IFD. write_at(w, &ifd, ifd_offset)?; Ok((next_ifd_offset_offset, strip_offsets_offset, tile_offsets_offset, jpeg_offset)) } // Returns the type, count, and encoded value. fn compose_value(value: &Value) -> Result<(u16, usize, Vec), Error> where E: Endian { match *value { Value::Byte(ref vec) => Ok((1, vec.len(), vec.clone())), Value::Ascii(ref vec) => { let mut buf = Vec::new(); for x in vec { buf.extend_from_slice(x); buf.push(0); } Ok((2, buf.len(), buf)) }, Value::Short(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu16(&mut buf, v)?; } Ok((3, vec.len(), buf)) }, Value::Long(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu32(&mut buf, v)?; } Ok((4, vec.len(), buf)) }, Value::Rational(ref vec) => { let mut buf = Vec::new(); for v in vec { E::writeu32(&mut buf, v.num)?; E::writeu32(&mut buf, v.denom)?; } Ok((5, vec.len(), buf)) }, Value::SByte(ref vec) => { let bytes = vec.iter().map(|x| *x as u8).collect(); Ok((6, vec.len(), bytes)) }, Value::Undefined(ref s, _) => Ok((7, s.len(), s.to_vec())), Value::SShort(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu16(&mut buf, v as u16)?; } Ok((8, vec.len(), buf)) }, Value::SLong(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu32(&mut buf, v as u32)?; } Ok((9, vec.len(), buf)) }, Value::SRational(ref vec) => { let mut buf = Vec::new(); for v in vec { E::writeu32(&mut buf, v.num as u32)?; E::writeu32(&mut buf, v.denom as u32)?; } Ok((10, vec.len(), buf)) }, Value::Float(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu32(&mut buf, v.to_bits())?; } Ok((11, vec.len(), buf)) }, Value::Double(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu64(&mut buf, v.to_bits())?; } Ok((12, vec.len(), buf)) }, Value::Unknown(_, _, _) => Err(Error::NotSupported("Cannot write unknown field types")), } } fn write_at(w: &mut W, buf: &[u8], offset: u32) -> io::Result<()> where W: Write + Seek { let orig = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(offset as u64))?; w.write_all(buf)?; w.seek(SeekFrom::Start(orig))?; Ok(()) } // Aligns `w` to the two-byte (word) boundary and returns the new offset. fn pad_and_get_offset(w: &mut W) -> Result where W: Write + Seek { let mut pos = w.seek(SeekFrom::Current(0))?; if pos >= (1 << 32) - 1 { return Err(Error::TooBig("Offset too large")); } if pos % 2 != 0 { w.write_all(&[0])?; pos += 1; } Ok(pos as u32) } fn get_offset(w: &mut W) -> Result where W: Write + Seek { let pos = w.seek(SeekFrom::Current(0))?; if pos as u32 as u64 != pos { return Err(Error::TooBig("Offset too large")); } Ok(pos as u32) } #[cfg(test)] mod tests { use std::io::Cursor; use super::*; #[test] fn primary() { let image_desc = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"Sample".to_vec()]), }; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&image_desc); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x07\x00\x00\x00\x1a\ \x00\x00\x00\x00\ Sample\0"; assert_eq!(buf.into_inner(), expected); } #[test] fn primary_exif_only() { let exif_ver = Field { tag: Tag::ExifVersion, ifd_num: In::PRIMARY, value: Value::Undefined(b"0231".to_vec(), 0), }; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&exif_ver); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x87\x69\x00\x04\x00\x00\x00\x01\x00\x00\x00\x1a\ \x00\x00\x00\x00\ \x00\x01\x90\x00\x00\x07\x00\x00\x00\x040231\ \x00\x00\x00\x00"; assert_eq!(buf.into_inner(), expected); } #[test] fn primary_tiff_tiled() { // This is not a valid TIFF tile (only for testing). let tiles: &[&[u8]] = &[b"TILE"]; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.set_tiles(tiles, In::PRIMARY); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x02\x01\x44\x00\x04\x00\x00\x00\x01\x00\x00\x00\x26\ \x01\x45\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\ \x00\x00\x00\x00\ TILE"; assert_eq!(buf.into_inner(), expected); } #[test] fn thumbnail_jpeg() { // This is not a valid JPEG data (only for testing). let jpeg = b"JPEG"; let desc = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"jpg".to_vec()]), }; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&desc); writer.set_jpeg(jpeg, In::THUMBNAIL); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x04jpg\x00\ \x00\x00\x00\x1a\ \x00\x02\x02\x01\x00\x04\x00\x00\x00\x01\x00\x00\x00\x38\ \x02\x02\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\ \x00\x00\x00\x00\ JPEG"; assert_eq!(buf.into_inner(), expected); } #[test] fn thumbnail_tiff() { // This is not a valid TIFF strip (only for testing). let desc = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"tif".to_vec()]), }; let strips: &[&[u8]] = &[b"STRIP"]; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&desc); writer.set_strips(strips, In::THUMBNAIL); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x04tif\x00\ \x00\x00\x00\x1a\ \x00\x02\x01\x11\x00\x04\x00\x00\x00\x01\x00\x00\x00\x38\ \x01\x17\x00\x04\x00\x00\x00\x01\x00\x00\x00\x05\ \x00\x00\x00\x00\ STRIP"; assert_eq!(buf.into_inner(), expected); } #[test] fn primary_and_thumbnail() { let image_desc = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"Sample".to_vec()]), }; let exif_ver = Field { tag: Tag::ExifVersion, ifd_num: In::PRIMARY, value: Value::Undefined(b"0231".to_vec(), 0), }; let gps_ver = Field { tag: Tag::GPSVersionID, ifd_num: In::PRIMARY, value: Value::Byte(vec![2, 3, 0, 0]), }; let interop_index = Field { tag: Tag::InteroperabilityIndex, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"ABC".to_vec()]), }; let jpeg = b"JPEG"; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&image_desc); writer.push_field(&exif_ver); writer.push_field(&gps_ver); writer.push_field(&interop_index); writer.set_jpeg(jpeg, In::THUMBNAIL); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x03\x01\x0e\x00\x02\x00\x00\x00\x07\x00\x00\x00\x74\ \x87\x69\x00\x04\x00\x00\x00\x01\x00\x00\x00\x32\ \x88\x25\x00\x04\x00\x00\x00\x01\x00\x00\x00\x50\ \x00\x00\x00\x7c\ \x00\x02\x90\x00\x00\x07\x00\x00\x00\x040231\ \xa0\x05\x00\x04\x00\x00\x00\x01\x00\x00\x00\x62\ \x00\x00\x00\x00\ \x00\x01\x00\x00\x00\x01\x00\x00\x00\x04\x02\x03\x00\x00\ \x00\x00\x00\x00\ \x00\x01\x00\x01\x00\x02\x00\x00\x00\x04ABC\0\ \x00\x00\x00\x00\ Sample\0\0\ \x00\x02\x02\x01\x00\x04\x00\x00\x00\x01\x00\x00\x00\x9a\ \x02\x02\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\ \x00\x00\x00\x00\ JPEG"; assert_eq!(buf.into_inner(), expected); } #[test] fn primary_thumbnail_and_2nd() { let desc0 = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"p".to_vec()]), }; let desc1 = Field { tag: Tag::ImageDescription, ifd_num: In::THUMBNAIL, value: Value::Ascii(vec![b"t".to_vec()]), }; let desc2 = Field { tag: Tag::ImageDescription, ifd_num: In(2), value: Value::Ascii(vec![b"2".to_vec()]), }; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&desc0); writer.push_field(&desc1); writer.push_field(&desc2); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x02p\x00\x00\x00\ \x00\x00\x00\x1a\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x02t\x00\x00\x00\ \x00\x00\x00\x2c\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x022\x00\x00\x00\ \x00\x00\x00\x00"; assert_eq!(buf.into_inner(), expected); } #[test] fn empty_file() { let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); assert_pat!(writer.write(&mut buf, false), Err(Error::InvalidFormat("At least one IFD must exist"))); } #[test] fn missing_primary() { let jpeg = b"JPEG"; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.set_jpeg(jpeg, In::THUMBNAIL); assert_pat!(writer.write(&mut buf, false), Err(Error::InvalidFormat("IFD must not be empty"))); } #[test] fn write_twice() { let image_desc = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"Sample".to_vec()]), }; let mut writer = Writer::new(); writer.push_field(&image_desc); let mut buf1 = Cursor::new(Vec::new()); writer.write(&mut buf1, false).unwrap(); let mut buf2 = Cursor::new(Vec::new()); writer.write(&mut buf2, false).unwrap(); assert_eq!(buf1.into_inner(), buf2.into_inner()); } #[test] fn compose_field_value() { let patterns = vec![ (Value::Byte(vec![1, 2]), (1, 2, vec![1, 2]), (1, 2, vec![1, 2])), (Value::Ascii(vec![b"a".to_vec(), b"b".to_vec()]), (2, 4, b"a\0b\0".to_vec()), (2, 4, b"a\0b\0".to_vec())), (Value::Short(vec![0x0102, 0x0304]), (3, 2, b"\x01\x02\x03\x04".to_vec()), (3, 2, b"\x02\x01\x04\x03".to_vec())), (Value::Long(vec![0x01020304, 0x05060708]), (4, 2, b"\x01\x02\x03\x04\x05\x06\x07\x08".to_vec()), (4, 2, b"\x04\x03\x02\x01\x08\x07\x06\x05".to_vec())), (Value::Rational(vec![(1, 2).into(), (3, 4).into()]), (5, 2, b"\0\0\0\x01\0\0\0\x02\0\0\0\x03\0\0\0\x04".to_vec()), (5, 2, b"\x01\0\0\0\x02\0\0\0\x03\0\0\0\x04\0\0\0".to_vec())), (Value::SByte(vec![-2, -128]), (6, 2, b"\xfe\x80".to_vec()), (6, 2, b"\xfe\x80".to_vec())), (Value::Undefined(b"abc".to_vec(), 0), (7, 3, b"abc".to_vec()), (7, 3, b"abc".to_vec())), (Value::SShort(vec![-2, -0x8000]), (8, 2, b"\xff\xfe\x80\x00".to_vec()), (8, 2, b"\xfe\xff\x00\x80".to_vec())), (Value::SLong(vec![-2, -0x80000000]), (9, 2, b"\xff\xff\xff\xfe\x80\x00\x00\x00".to_vec()), (9, 2, b"\xfe\xff\xff\xff\x00\x00\x00\x80".to_vec())), (Value::SRational(vec![(-1, -2).into(), (-3, -4).into()]), (10, 2, b"\xff\xff\xff\xff\xff\xff\xff\xfe\ \xff\xff\xff\xfd\xff\xff\xff\xfc".to_vec()), (10, 2, b"\xff\xff\xff\xff\xfe\xff\xff\xff\ \xfd\xff\xff\xff\xfc\xff\xff\xff".to_vec())), (Value::Float(vec![2.5, -0.5]), (11, 2, b"\x40\x20\x00\x00\xbf\x00\x00\x00".to_vec()), (11, 2, b"\x00\x00\x20\x40\x00\x00\x00\xbf".to_vec())), (Value::Double(vec![2.5, -0.5]), (12, 2, b"\x40\x04\x00\x00\x00\x00\x00\x00\ \xbf\xe0\x00\x00\x00\x00\x00\x00".to_vec()), (12, 2, b"\x00\x00\x00\x00\x00\x00\x04\x40\ \x00\x00\x00\x00\x00\x00\xe0\xbf".to_vec())), ]; for (val, be, le) in patterns.into_iter() { assert_eq!(compose_value::(&val).unwrap(), be); assert_eq!(compose_value::(&val).unwrap(), le); } } } kamadak-exif-0.5.5/tests/exif.heic000064400000000000000000000011761046102023000151110ustar 00000000000000ftypheicmif1heiclmeta!hdlrpictpitm4ilocD@+S8iinfinfehvc1infeExifiprpipcorhvcC @ @!&B[$  !"Dr"@ispe@@ipmairefcdscmdat('5Csofu>w3qc쓧MoKcbKY@aAhNyq@?.\ Nl _"EDVʟK$1æspW!z]E!D)̇1sU䆖%MM*18i&0231libheif + kamadak-exifkamadak-exif-0.5.5/tests/exif.jpg000064400000000000000000000023541046102023000147600ustar 00000000000000JFIFHH^ExifMM* Vbj(2riTest imageFHH2016:05:04 03:02:010230JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?] W Slw(C     C    ?pzٳaMz.ʵ^r9GCP@ 16\T9AV?m9 ʯՔw|8.hۏڸGkamadak-exif-0.5.5/tests/exif.png000064400000000000000000000004171046102023000147620ustar 00000000000000PNG  IHDRKdeXIfMM* ht|(1iV0232Test imageHHimplex + kamadak-exifPLTEٟIDATco ]ncr ЕIENDB`kamadak-exif-0.5.5/tests/exif.tif000064400000000000000000000011671046102023000147630ustar 00000000000000MM* @; (iV0230Test imageHHhTest thumbnailkamadak-exif-0.5.5/tests/exif.webp000064400000000000000000000006201046102023000151270ustar 00000000000000RIFFWEBPVP8X VP8 *%3 WtgΜg8_/"꥜ ˿ѕcn\~0NGo_;گٍ<_G5ND9徬q w;yeؼS@Q^rz@EXIFMM* hrz(1iV0232WebP testHHGIMP + implex + kamadak-exifkamadak-exif-0.5.5/tests/platform.rs000064400000000000000000000027661046102023000155240ustar 00000000000000// // Copyright (c) 2016 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. // use std::mem; // This library assumes that usize is not smaller than u32. #[test] fn size_of_usize() { assert!(mem::size_of::() >= mem::size_of::()); } kamadak-exif-0.5.5/tests/rwrcmp.rs000064400000000000000000000203171046102023000152020ustar 00000000000000// // 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. // //! Read, write, re-read, and compare the results. //! //! This test can be also compiled as a command-line program. extern crate exif; use std::env; use std::fs::File; use std::io::{BufReader, Cursor}; use std::path::Path; #[cfg(not(test))] use exif::Error; use exif::{Exif, In, Reader, Value, Tag}; use exif::experimental::Writer; #[test] fn exif_heic() { rwr_compare("tests/exif.heic"); } #[test] fn exif_jpg() { rwr_compare("tests/exif.jpg"); } #[test] fn exif_png() { rwr_compare("tests/exif.png"); } #[test] fn exif_tif() { rwr_compare("tests/exif.tif"); } #[test] fn exif_webp() { rwr_compare("tests/exif.webp"); } fn main() { for path in env::args_os().skip(1) { rwr_compare(&path); } } fn rwr_compare

(path: P) where P: AsRef { let path = path.as_ref(); // Read. let file = File::open(path).unwrap(); let mut bufreader = BufReader::new(&file); #[cfg(test)] let exif1 = Reader::new().read_from_container(&mut bufreader).unwrap(); #[cfg(not(test))] 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(&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 exif1.fields() { writer.push_field(f); } if let Some(ref strips) = strips { writer.set_strips(strips, In::PRIMARY); } if let Some(ref tn_strips) = tn_strips { writer.set_strips(tn_strips, In::THUMBNAIL); } if let Some(ref tiles) = tiles { writer.set_tiles(tiles, In::PRIMARY); } if let Some(ref tn_jpeg) = tn_jpeg { writer.set_jpeg(tn_jpeg, In::THUMBNAIL); } let mut out = Cursor::new(Vec::new()); #[cfg(test)] writer.write(&mut out, exif1.little_endian()).unwrap(); #[cfg(not(test))] match writer.write(&mut out, exif1.little_endian()) { Ok(_) => {}, Err(Error::NotSupported(_)) => { println!("{}: Contains unknown type", path.display()); return; }, e => e.unwrap(), } let out = out.into_inner(); // Re-read. let exif2 = Reader::new().read_raw(out).unwrap(); // Sort the fields (some files have wrong tag order). let mut fields1 = exif1.fields().collect::>(); fields1.sort_by_key(|f| (f.ifd_num, f.tag)); let mut fields2 = exif2.fields().collect::>(); fields2.sort_by_key(|f| (f.ifd_num, f.tag)); // Compare. assert_eq!(fields1.len(), fields2.len()); for (f1, f2) in fields1.iter().zip(fields2.iter()) { assert_eq!(f1.tag, f2.tag); assert_eq!(f1.ifd_num, f2.ifd_num); match f1.tag { Tag::StripOffsets | Tag::TileOffsets | Tag::JPEGInterchangeFormat => continue, _ => {}, } compare_field_value(&f1.value, &f2.value); } 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. fn compare_field_value(value1: &Value, value2: &Value) { // The TIFF standard requires that BYTE, SHORT, or LONG values should // be accepted for any unsigned integer field. if let (Some(it1), Some(it2)) = (value1.iter_uint(), value2.iter_uint()) { assert!(it1.eq(it2)); return; } // Compare other fields strictly. match (value1, value2) { (&Value::Ascii(ref v1), &Value::Ascii(ref v2)) => assert_eq!(v1, v2), (&Value::Rational(ref v1), &Value::Rational(ref v2)) => { assert_eq!(v1.len(), v2.len()); for (r1, r2) in v1.iter().zip(v2.iter()) { assert_eq!(r1.num, r2.num); assert_eq!(r1.denom, r2.denom); } }, (&Value::SByte(ref v1), &Value::SByte(ref v2)) => assert_eq!(v1, v2), (&Value::Undefined(ref v1, _), &Value::Undefined(ref v2, _)) => assert_eq!(v1, v2), (&Value::SShort(ref v1), &Value::SShort(ref v2)) => assert_eq!(v1, v2), (&Value::SLong(ref v1), &Value::SLong(ref v2)) => assert_eq!(v1, v2), (&Value::SRational(ref v1), &Value::SRational(ref v2)) => { assert_eq!(v1.len(), v2.len()); for (r1, r2) in v1.iter().zip(v2.iter()) { assert_eq!(r1.num, r2.num); assert_eq!(r1.denom, r2.denom); } }, (&Value::Float(ref v1), &Value::Float(ref v2)) => assert_eq!(v1, v2), (&Value::Double(ref v1), &Value::Double(ref v2)) => assert_eq!(v1, v2), _ => panic!("{:?} != {:?}", value1, value2), } } 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 = 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 = 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(exif: &Exif, ifd_num: In) -> Option> { let offsets = exif.get_field(Tag::TileOffsets, ifd_num) .and_then(|f| f.value.iter_uint()); let counts = exif.get_field(Tag::TileByteCounts, ifd_num) .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 tile offsets and byte counts"), }; assert_eq!(offsets.len(), counts.len()); let buf = exif.buf(); let strips = offsets.zip(counts).map( |(ofs, cnt)| &buf[ofs as usize .. (ofs + cnt) as usize]).collect(); Some(strips) } 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 = 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 = exif.buf(); Some(&buf[offset..offset+len]) } kamadak-exif-0.5.5/tests/yaminabe.tif000064400000000000000000000012031046102023000156040ustar 00000000000000MM* R Xld=Sli%( r B0231Test imageHA x??0dUh(FS)7":Й(V@@2T p=T \ Ѽ \އ"{Y`@Ja0d [TdTest thumbnail vTest 2nd IFDkamadak-exif-0.5.5/tests/yaminale.tif000064400000000000000000000012031046102023000156160ustar 00000000000000II* R Xld=Sli%( r B0231Test imageHA x??0dUh(FS)7":Й(V@@2T p=T \ Ѽ \އ"{Y`@Ja0d [TdTest thumbnail vTest 2nd IFD