exif-rs

repository·master·Indexed 42 days ago

https://github.com/kamadak/exif-rs

A pure-Rust library for parsing Exif metadata from raw data blocks or image formats including JPEG, PNG, WebP, HEIF (HEIC, AVIF), and TIFF. Version 0.6.1 supports Exif 2.32, DCF 2.0, and TIFF Revision 6.0. It provides a Reader for container extraction, a Tag system for identifying metadata contexts (Tiff, Exif, Gps, Interop), and an experimental Writer API.

Tokens
9.3K
Snippets
32
Records
40
Agent score
52%

What's inside exif-rs

  1. Supported image formats for Exif parsing

    master

    The library can parse raw Exif data blocks or read Exif data directly from the following image formats:

    • TIFF and some RAW image formats based on it
    • JPEG
    • HEIF and coding-specific variations (including HEIC and AVIF)
    • PNG
    • WebP
  2. How the Writer synthesizes Exif structure

    master

    The Writer manages the complex relationships between different IFDs (Primary, Exif, GPS, Interop). When you push a field, the Writer uses the Field.ifd_num and the Tag's context to place it in the correct directory.

    When write is called, the Writer automatically handles:

    1. Pointer Synthesis: It calculates and writes the necessary pointer tags (like ExifIFDPointer) to link the Primary IFD to the Exif, GPS, or Interop IFDs.
    2. Data Alignment: It ensures all values and offsets are written on proper word boundaries.
    3. Image Data Offsets: It calculates and writes the actual offsets for StripOffsets, TileOffsets, and JPEGInterchangeFormat based on the raw data provided via set_strips, set_tiles, or set_jpeg.
  3. Identify the context of an Exif tag using `Context`

    master

    The Context enum indicates how a tag number should be interpreted. It categorizes tags into different IFDs (Image File Directories):

    • Context::Tiff: TIFF attributes defined in the TIFF Rev. 6.0 specification (0th/1st IFD).
    • Context::Exif: Exif attributes (Exif IFD).
    • Context::Gps: GPS attributes (GPS IFD).
    • Context::Interop: Interoperability attributes (Interoperability IFD).
  4. Implement generic endian-aware functions

    master

    Because Endian is a trait, you can write functions that work with any endianness using generic type parameters. This is useful for building parsers that can switch between big-endian and little-endian modes based on the EXIF header metadata.

    use exif::Endian;
    
    fn parse_value<E: Endian>(data: &[u8]) -> u16 {
        E::loadu16(data, 0)
    }
    
    let val_be = parse_value::<BigEndian>(&[0x01, 0x02]);    // 0x0102
    let val_le = parse_value::<LittleEndian>(&[0x01, 0x02]); // 0x0201
  5. Represent parsed Exif data with the `Value` enum

    master

    The Value enum is the primary type used to represent the different data types found in TIFF/Exif fields. It can hold various formats including integers (Byte, Short, Long, SByte, SShort, SLong), rationals (Rational, SRational), floating-point numbers (Float, Double), ASCII strings, and Undefined byte slices.

    To display a value in a format specific to its Exif tag (e.g., converting a Short to a human-readable string like "inch" for a resolution unit), use the display_as method. Note that display_as provides the raw value; if you need the value with its associated unit, use Field::display_value instead.

    use exif::{Value, Tag};
    
    // Example: Displaying a value according to its tag
    let val = Value::Short(vec![2]);
    assert_eq!(val.display_as(Tag::ResolutionUnit).to_string(), "inch");
    
    // Example: Displaying ExifVersion
    let val = Value::Undefined(b"0231".to_vec(), 0);
    assert_eq!(val.display_as(Tag::ExifVersion).to_string(), "2.31");
  6. Understand how Exif tag values are formatted for display

    master

    When displaying Exif data, the library uses specific formatting logic for different tag types. Understanding these patterns helps in parsing or debugging the string output of the library:

    • Rational/SRational (e.g., Brightness, Temperature, Humidity): These are typically converted to f64. If the denominator is 0xffffffff (for Rational) or -1 (for SRational), the value is displayed as unknown.
    • SubjectArea/SubjectLocation: These are formatted as geometric descriptions: rectangle (x=..., y=..., w=..., h=...), circle (x=..., y=..., d=...), or point (x=..., y=...).
    • GPS Data:
      • GPSVersionID: Formatted as major.minor.revision.build (e.g., 1.0.0.0).
      • GPSLatitude/Longitude: Formatted as deg min sec (e.g., 45.0 deg 30.0 min 15.0 sec).
      • GPSTimeStamp: Formatted as HH:MM:SS with zero-padding.
      • GPSDateStamp: Formatted as YYYY-MM-DD.
    • ASCII/Undefined: ASCII strings are wrapped in quotes and escape special characters (like \ or "). Non-printable characters are escaped using hex notation (e.g., \x0a). Undefined bytes are displayed as a hex string prefixed with 0x (e.g., 0x61626300ff).
  7. Understand IFD numbers and types

    master

    In TIFF/Exif, data is organized into Image File Directories (IFDs). The In struct represents these indices:

    • In::PRIMARY (index 0): The main image data.
    • In::THUMBNAIL (index 1): The thumbnail data.
    • Other indices represent additional IFDs.
    use exif::In;
    assert_eq!(In::PRIMARY.index(), 0);
    assert_eq!(In::THUMBNAIL.index(), 1);
  8. Access and process Exif field values

    master

    Exif data is accessed via Exif::get_field using a Tag and an In location (e.g., In::PRIMARY). To process values programmatically, use the Value enum rather than string representations.

    Key patterns:

    • Unsigned Integers: Use Value::get_uint(index) to retrieve unsigned integer values. This is recommended because the Exif standard suggests readers should accept BYTE, SHORT, or LONG for any unsigned integer field.
    • Rationals: Access rational values by matching on Value::Rational.
    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();
    
    // Accessing Orientation (Unsigned Integer)
    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"),
    }
    
    // Accessing XResolution (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"),
    }
  9. Parse Exif data from an image container

    master

    To extract Exif attributes from an image file (supporting formats like TIFF, JPEG, HEIF, PNG, and WebP), use Reader::read_from_container. This method accepts any type that implements std::io::Read.

    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));
        }
    }
  10. Install kamadak-exif via Cargo

    master

    To use this library in your Rust project, add kamadak-exif to your Cargo.toml dependencies. Note that while the canonical crate name is exif, it is published to crates.io as kamadak-exif to avoid naming conflicts.

    [dependencies]
    kamadak-exif = "x.y.z"
  11. Check if a buffer is a JPEG

    master

    Use is_jpeg to quickly determine if a byte slice starts with the standard JPEG Start of Image (SOI) marker (0xFF 0xD8).

    let buf = &[0xff, 0xd8, 0x01, 0x02];
    if is_jpeg(buf) {
        // The buffer is a JPEG
    }
  12. Add Exif fields using push_field

    master

    Use push_field to append a Field to the Writer. Fields can be appended in any order, but duplicate fields must not be appended.

    Certain tags are automatically synthesized by the Writer and should not be manually pushed:

    • ExifIFDPointer, GPSInfoIFDPointer, InteropIFDPointer (synthesized via IFD relationships)
    • StripOffsets, StripByteCounts, TileOffsets, TileByteCounts (synthesized from strip/tile data)
    • JPEGInterchangeFormat, JPEGInterchangeFormatLength (synthesized from JPEG data)
    writer.push_field(&field);