kamadak-exif-0.6.1/.cargo_vcs_info.json0000644000000001360000000000100133620ustar { "git": { "sha1": "ba531e6bb523bf7c01849cf8e679beff4ef960b0" }, "path_in_vcs": "" }kamadak-exif-0.6.1/Cargo.lock0000644000000006020000000000100113330ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "kamadak-exif" version = "0.6.1" 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.6.1/Cargo.toml0000644000000026330000000000100113640ustar # 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 = "2021" rust-version = "1.60" name = "kamadak-exif" version = "0.6.1" authors = ["KAMADA Ken'ichi "] build = false autobins = false autoexamples = false autotests = false autobenches = false 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" path = "src/lib.rs" [[example]] name = "dumpexif" path = "examples/dumpexif.rs" [[example]] name = "reading" path = "examples/reading.rs" [[test]] name = "platform" path = "tests/platform.rs" [[test]] name = "rwrcmp" path = "tests/rwrcmp.rs" [dependencies.mutate_once] version = "0.1.1" kamadak-exif-0.6.1/Cargo.toml.orig000064400000000000000000000011131046102023000150350ustar 00000000000000[package] name = "kamadak-exif" version = "0.6.1" authors = ["KAMADA Ken'ichi "] edition = "2021" rust-version = "1.60" 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.6.1/LICENSE000064400000000000000000000024101046102023000131540ustar 00000000000000Copyright (c) 2016-2023 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. kamadak-exif-0.6.1/NEWS000064400000000000000000000100511046102023000126460ustar 000000000000000.6.1 (2024-11-07) - `exif::Error` has been changed to be `Sync` again. It was unintentionally not so in 0.6. 0.6 (2024-11-01) - Rust 1.60 or later is now required to build this package. - `From` and `From` implementations for `f32` and `f64` have been removed. Use `Rational::to_f64` and so on. - `Reader::continue_on_error` has been added to enable continue-on-error mode for parsing slightly broken Exif data. In this mode, parser may return `Error::PartialResult`. 0.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.6.1/README000064400000000000000000000025351046102023000130370ustar 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.60 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.6.1/examples/dumpexif.rs000064400000000000000000000047051046102023000161650ustar 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)?; // To parse strictly: // let exif = exif::Reader::new() // .read_from_container(&mut BufReader::new(&file))?; // To parse with continue-on-error mode: let exif = exif::Reader::new() .continue_on_error(true) .read_from_container(&mut BufReader::new(&file)) .or_else(|e| e.distill_partial_result(|errors| { eprintln!("{}: {} warning(s)", path.display(), errors.len()); errors.iter().for_each(|e| eprintln!(" {}", e)); }))?; 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.6.1/examples/reading.rs000064400000000000000000000065431046102023000157570ustar 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 { 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.6.1/src/doc.rs000064400000000000000000000142501046102023000140560ustar 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 /// # News #[doc = include_str!("../NEWS")] pub mod news {} /// # Upgrade Guide /// /// ## Upgrade from 0.5.x to 0.6.x /// /// ### API compatibilities /// /// * `From` and `From` implementations for `f32` /// and `f64` have been removed. /// Use `Rational::to_f64` and so on. /// /// ### Compiler /// /// * Rust 1.60 or later is required. /// /// ## 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.6.1/src/endian.rs000064400000000000000000000152741046102023000145560ustar 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.6.1/src/error.rs000064400000000000000000000126641046102023000144510ustar 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; use std::sync::Mutex; /// 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), /// Partially-parsed result and errors. This can be returned only when /// `Reader::continue_on_error` is enabled. PartialResult(PartialResult), } impl Error { /// Extracts `Exif` and `Vec` from `Error::PartialResult`. /// /// If `self` is `Error::PartialResult`, /// ignored errors are passed to `f` as `Vec` and /// partially-parsed result is retuend in `Ok`. /// Otherwise, `Err(self)` is returned. pub fn distill_partial_result(self, f: F) -> Result where F: FnOnce(Vec) { if let Error::PartialResult(partial) = self { let (exif, errors) = partial.into_inner(); f(errors); Ok(exif) } else { Err(self) } } } 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), Error::PartialResult(ref pr) => write!(f, "Partial result with {} fields and {} errors", pr.0.0.lock().expect("should not panic").fields().len(), pr.0.1.len()), } } } 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, Error::PartialResult(_) => None, } } } /// Partially-parsed result and errors. pub struct PartialResult(Box<(Mutex, Vec)>); impl PartialResult { pub(crate) fn new(exif: crate::Exif, errors: Vec) -> Self { Self(Box::new((Mutex::new(exif), errors))) } /// Returns partially-parsed `Exif` and ignored `Error`s. pub fn into_inner(self) -> (crate::Exif, Vec) { let (exif, errors) = *self.0; (exif.into_inner().expect("should not panic"), errors) } } impl fmt::Debug for PartialResult { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "PartialResult(Exif({} fields), {:?})", self.0.0.lock().expect("should not panic").fields().len(), self.0.1) } } #[cfg(test)] mod tests { use super::*; // Check compatibility with anyhow::Error, which requires Send, Sync, // and 'static on error types. #[test] fn is_send_sync_static() { let _: Box = Box::new(Error::InvalidFormat("test")); } } kamadak-exif-0.6.1/src/isobmff.rs000064400000000000000000000500411046102023000147340ustar 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, 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.6.1/src/jpeg.rs000064400000000000000000000124531046102023000142410ustar 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.6.1/src/lib.rs000064400000000000000000000106141046102023000140570ustar 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) for API incompatibilities. pub use error::{Error, PartialResult}; 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.6.1/src/png.rs000064400000000000000000000102761046102023000141010ustar 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.6.1/src/reader.rs000064400000000000000000000334451046102023000145620ustar 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, PartialResult}; 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 { continue_on_error: bool, } impl Reader { /// Constructs a new `Reader`. pub fn new() -> Self { Self { continue_on_error: false, } } /// Sets the option to continue parsing on non-fatal errors. /// /// When this option is enabled, the parser will not stop on non-fatal /// errors and returns the results as far as they can be parsed. /// In such a case, `read_raw` and `read_from_container` /// return `Error::PartialResult`. /// The partial result and ignored errors can be obtained by /// [`Error::distill_partial_result`] or [`PartialResult::into_inner`]. /// /// Note that a hard error (other than `Error::PartialResult`) may be /// returned even if this option is enabled. /// /// # Examples /// ``` /// # use std::fmt::{Display, Formatter, Result}; /// # fn main() -> std::result::Result<(), Box> { /// use exif::Reader; /// let file = std::fs::File::open("tests/exif.jpg")?; /// let exif = Reader::new() /// .continue_on_error(true) /// .read_from_container(&mut std::io::BufReader::new(&file)) /// .or_else(|e| e.distill_partial_result(|errors| { /// errors.iter().for_each(|e| eprintln!("Warning: {}", e)); /// }))?; /// # Ok(()) } /// ``` pub fn continue_on_error(&mut self, continue_on_error: bool) -> &mut Self { self.continue_on_error = continue_on_error; 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.continue_on_error = self.continue_on_error.then(|| Vec::new()); parser.parse(&data)?; let entry_map = parser.entries.iter().enumerate() .map(|(i, e)| (e.ifd_num_tag(), i)).collect(); let exif = Exif { buf: data, entries: parser.entries, entry_map: entry_map, little_endian: parser.little_endian, }; match parser.continue_on_error { Some(v) if !v.is_empty() => Err(Error::PartialResult(PartialResult::new(exif, v))), _ => Ok(exif), } } /// 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\""); } #[test] fn continue_on_error() { let data = b"MM\0\x2a\0\0\0\x08\ \0\x02\x01\x00\0\x03\0\0\0\x01\0\x14\0\0\ \x01\x01\0\x03\0\0\0\x01\0\x15\0"; let result = Reader::new() .continue_on_error(true) .read_raw(data.to_vec()); if let Err(Error::PartialResult(partial)) = result { let (exif, errors) = partial.into_inner(); assert_pat!(exif.fields().collect::>().as_slice(), [Field { tag: Tag::ImageWidth, ifd_num: In(0), value: Value::Short(_) }]); assert_pat!(&errors[..], [Error::InvalidFormat("Truncated IFD")]); } else { panic!("partial result expected"); } } } kamadak-exif-0.6.1/src/tag.rs000064400000000000000000001543721046102023000140760ustar 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.6.1/src/tiff.rs000064400000000000000000000771101046102023000142450ustar 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::util::{atou16, ctou32}; use crate::value; use crate::value::Value; use crate::value::get_type_info; // 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, // `Some` to enable the option and `None` to disable it. pub continue_on_error: Option>, } impl Parser { pub fn new() -> Self { Self { entries: Vec::new(), little_endian: false, continue_on_error: None, } } 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_header::(data) }, TIFF_LE => { self.little_endian = true; self.parse_header::(data) }, _ => Err(Error::InvalidFormat("Invalid TIFF byte order")), } } fn parse_header(&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 ifd_offset = E::loadu32(data, 4) as usize; self.parse_body::(data, ifd_offset) .or_else(|e| self.check_error(e)) } fn parse_body(&mut self, data: &[u8], mut ifd_offset: usize) -> Result<(), Error> where E: Endian { 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], mut 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; offset += 2; // Array of entries. for _ in 0..count { if data.len() - offset < 12 { return Err(Error::InvalidFormat("Truncated IFD")); } let entry = Self::parse_ifd_entry::(data, offset); offset += 12; let (tag, val) = match entry { Ok(x) => x, Err(e) => { self.check_error(e)?; continue; }, }; // No infinite recursion will occur because the context is not // recursively defined. let tag = Tag(ctx, tag); let child_ctx = match tag { Tag::ExifIFDPointer => Context::Exif, Tag::GPSInfoIFDPointer => Context::Gps, Tag::InteropIFDPointer => Context::Interop, _ => { self.entries.push(IfdEntry { field: Field { tag: tag, ifd_num: In(ifd_num), value: val }.into()}); continue; }, }; self.parse_child_ifd::(data, val, child_ctx, ifd_num) .or_else(|e| self.check_error(e))?; } // Offset to the next IFD. if data.len() - offset < 4 { return Err(Error::InvalidFormat("Truncated next IFD offset")); } let next_ifd_offset = E::loadu32(data, offset); Ok(next_ifd_offset as usize) } fn parse_ifd_entry(data: &[u8], offset: usize) -> Result<(u16, Value), Error> where E: Endian { // The size of entry has been checked in parse_ifd(). let tag = E::loadu16(data, offset); let typ = E::loadu16(data, offset + 2); let cnt = E::loadu32(data, offset + 4); let valofs_at = offset + 8; let (unitlen, _parser) = get_type_info::(typ); let vallen = unitlen.checked_mul(cnt as usize).ok_or( Error::InvalidFormat("Invalid entry count"))?; let 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) }; Ok((tag, val)) } fn parse_child_ifd(&mut self, data: &[u8], mut pointer: Value, ctx: Context, ifd_num: u16) -> Result<(), Error> where E: Endian { // The pointer is not yet parsed, so do it here. IfdEntry::parse_value::(&mut 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")), } } fn check_error(&mut self, err: Error) -> Result<(), Error> { match self.continue_on_error { Some(ref mut v) => Ok(v.push(err)), None => Err(err), } } } 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 parse_ifd_entry() { // BYTE (type == 1) let data = b"\x02\x03\x00\x01\0\0\0\x04ABCD"; assert_pat!(Parser::parse_ifd_entry::(data, 0).unwrap(), (0x0203, Value::Unknown(1, 4, 8))); let data = b"\x02\x03\x00\x01\0\0\0\x05\0\0\0\x0cABCDE"; assert_pat!(Parser::parse_ifd_entry::(data, 0).unwrap(), (0x0203, Value::Unknown(1, 5, 12))); let data = b"\x02\x03\x00\x01\0\0\0\x05\0\0\0\x0cABCD"; assert_err_pat!(Parser::parse_ifd_entry::(data, 0), Error::InvalidFormat("Truncated field value")); // SHORT (type == 3) let data = b"X\x04\x05\x00\x03\0\0\0\x02ABCD"; assert_pat!(Parser::parse_ifd_entry::(data, 1).unwrap(), (0x0405, Value::Unknown(3, 2, 9))); let data = b"X\x04\x05\x00\x03\0\0\0\x03\0\0\0\x0eXABCDEF"; assert_pat!(Parser::parse_ifd_entry::(data, 1).unwrap(), (0x0405, Value::Unknown(3, 3, 14))); let data = b"X\x04\x05\x00\x03\0\0\0\x03\0\0\0\x0eXABCDE"; assert_err_pat!(Parser::parse_ifd_entry::(data, 1), Error::InvalidFormat("Truncated field value")); // Really unknown let data = b"X\x01\x02\x03\x04\x05\x06\x07\x08ABCD"; assert_pat!(Parser::parse_ifd_entry::(data, 1).unwrap(), (0x0102, Value::Unknown(0x0304, 0x05060708, 9))); } #[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"); } #[test] fn continue_on_error() { macro_rules! define_test { { data: $data:expr, fields: [$($fields:pat),*], errors: [$first_error:pat $(, $rest_errors:pat)*] } => { let data = $data; let mut parser = Parser::new(); assert_err_pat!(parser.parse(data), $first_error); let mut parser = Parser::new(); parser.continue_on_error = Some(Vec::new()); parser.parse(data).unwrap(); assert_eq!(parser.little_endian, false); let mut entries = parser.entries.iter(); $( assert_pat!(entries.next().unwrap() .ref_field(data, parser.little_endian), $fields); )* assert_pat!(entries.next(), None); let mut errors = parser.continue_on_error.as_ref().unwrap().iter(); assert_pat!(errors.next().unwrap(), $first_error); $( assert_pat!(errors.next().unwrap(), $rest_errors); )* assert_pat!(errors.next(), None); } } // 0th IFD is missing. define_test! { data: b"MM\0\x2a\0\0\0\x08", fields: [], errors: [Error::InvalidFormat("Truncated IFD count")] } // 2nd entry is truncated. define_test! { data: b"MM\0\x2a\0\0\0\x08\ \0\x02\x01\x00\0\x03\0\0\0\x01\0\x14\0\0\ \x01\x01\0\x03\0\0\0\x01\0\x15\0", fields: [Field { tag: Tag::ImageWidth, ifd_num: In(0), value: Value::Short(_) }], errors: [Error::InvalidFormat("Truncated IFD")] } // 1st entry broken. define_test! { data: b"MM\0\x2a\0\0\0\x08\ \0\x02\x01\x00\0\x03\0\0\0\x03\0\0\0\x21\ \x01\x01\0\x03\0\0\0\x01\0\x15\0\0\ \0\0\0\0", fields: [Field { tag: Tag::ImageLength, ifd_num: In(0), value: Value::Short(_) }], errors: [Error::InvalidFormat("Truncated field value")] } // Exif IFD has non-zero next IFD offset. // Top-level next IFD is also broken. define_test! { data: b"MM\0\x2a\0\0\0\x08\ \0\x02\x87\x69\0\x04\0\0\0\x01\0\0\0\x26\ \xfd\xe8\0\x09\0\0\0\x01\xfe\xdc\xba\x98\ \xff\xff\xff\xff\ \0\x01\x90\x00\0\x07\0\0\0\x04\x00\x02\x03\x02\ \0\0\0\x01", fields: [Field { tag: Tag::ExifVersion, ifd_num: In(0), value: Value::Undefined(_, _) }, Field { tag: Tag(Context::Tiff, 65000), ifd_num: In(0), value: Value::SLong(_) }], errors: [Error::InvalidFormat("Unexpected next IFD"), Error::InvalidFormat("Truncated IFD count")] } // Exif IFD pointer has a bad type. define_test! { data: b"MM\0\x2a\0\0\0\x08\ \0\x02\x87\x69\0\x09\0\0\0\x01\0\0\0\x26\ \xfd\xe8\0\x06\0\0\0\x03\xfe\xdc\xba\x98\ \0\0\0\0\ \0\x01\x90\x00\0\x07\0\0\0\x04\x00\x02\x03\x02\ \0\0\0\x01", fields: [Field { tag: Tag(Context::Tiff, 65000), ifd_num: In(0), value: Value::SByte(_) }], errors: [Error::InvalidFormat("Invalid pointer")] } // Exif IFD pointer is empty. define_test! { data: b"MM\0\x2a\0\0\0\x08\ \0\x02\x87\x69\0\x04\0\0\0\x00\0\0\0\x26\ \xfd\xe8\0\x08\0\0\0\x02\xfe\xdc\xba\x98\ \0\0\0\0\ \0\x01\x90\x00\0\x07\0\0\0\x04\x00\x02\x03\x02\ \0\0\0\x01", fields: [Field { tag: Tag(Context::Tiff, 65000), ifd_num: In(0), value: Value::SShort(_) }], errors: [Error::InvalidFormat("Invalid pointer")] } } } kamadak-exif-0.6.1/src/tmacro.rs000064400000000000000000000044121046102023000145750ustar 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.6.1/src/util.rs000064400000000000000000000142051046102023000142660ustar 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.6.1/src/value.rs000064400000000000000000001024111046102023000144220ustar 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) } } /// 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) } } // 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!(Rational::from((1, 2)).to_f64(), 0.5); assert_eq!(Rational::from((1, u32::MAX)).to_f64(), 2.3283064370807974e-10); assert_eq!(Rational::from((u32::MAX, 1)).to_f64(), u32::MAX as f64); assert_eq!(Rational::from((u32::MAX - 1, u32::MAX)).to_f64(), 0.9999999997671694); assert_eq!(Rational::from((u32::MAX, u32::MAX - 1)).to_f64(), 1.0000000002328306); assert_eq!(Rational::from((1, 0)).to_f64(), f64::INFINITY); assert!(Rational::from((0, 0)).to_f64().is_nan()); assert_eq!(SRational::from((1, 2)).to_f64(), 0.5); assert_eq!(SRational::from((-1, 2)).to_f64(), -0.5); assert_eq!(SRational::from((1, -2)).to_f64(), -0.5); assert_eq!(SRational::from((-1, -2)).to_f64(), 0.5); assert_eq!(SRational::from((1, 0)).to_f64(), f64::INFINITY); assert_eq!(SRational::from((-1, 0)).to_f64(), 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!(Rational::from((1, 16777217)).to_f32(), 5.960464e-8); } } kamadak-exif-0.6.1/src/webp.rs000064400000000000000000000123051046102023000142450ustar 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.6.1/src/writer.rs000064400000000000000000000753361046102023000146410ustar 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 { assert_eq!(compose_value::(&val).unwrap(), be); assert_eq!(compose_value::(&val).unwrap(), le); } } } kamadak-exif-0.6.1/tests/exif.heic000064400000000000000000000011761046102023000151060ustar 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.6.1/tests/exif.jpg000064400000000000000000000023541046102023000147550ustar 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.6.1/tests/exif.png000064400000000000000000000004171046102023000147570ustar 00000000000000PNG  IHDRKdeXIfMM* ht|(1iV0232Test imageHHimplex + kamadak-exifPLTEٟIDATco ]ncr ЕIENDB`kamadak-exif-0.6.1/tests/exif.tif000064400000000000000000000011671046102023000147600ustar 00000000000000MM* @; (iV0230Test imageHHhTest thumbnailkamadak-exif-0.6.1/tests/exif.webp000064400000000000000000000006201046102023000151240ustar 00000000000000RIFFWEBPVP8X VP8 *%3 WtgΜg8_/"꥜ ˿ѕcn\~0NGo_;گٍ<_G5ND9徬q w;yeؼS@Q^rz@EXIFMM* hrz(1iV0232WebP testHHGIMP + implex + kamadak-exifkamadak-exif-0.6.1/tests/platform.rs000064400000000000000000000027661046102023000155210ustar 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.6.1/tests/rwrcmp.rs000064400000000000000000000203171046102023000151770ustar 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.6.1/tests/yaminabe.tif000064400000000000000000000012031046102023000156010ustar 00000000000000MM* R Xld=Sli%( r B0231Test imageHA x??0dUh(FS)7":Й(V@@2T p=T \ Ѽ \އ"{Y`@Ja0d [TdTest thumbnail vTest 2nd IFDkamadak-exif-0.6.1/tests/yaminale.tif000064400000000000000000000012031046102023000156130ustar 00000000000000II* R Xld=Sli%( r B0231Test imageHA x??0dUh(FS)7":Й(V@@2T p=T \ Ѽ \އ"{Y`@Ja0d [TdTest thumbnail vTest 2nd IFD