or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

ExifRead

Easy to use Python module to extract Exif metadata from digital image files. Supports multiple image formats including TIFF, JPEG, JPEG XL, PNG, Webp, HEIC, and RAW files with zero external dependencies.

Package Information

  • Package Name: ExifRead
  • Language: Python
  • Installation: pip install exifread
  • Supported Python: 3.7 to 3.13
  • License: BSD-3-Clause
  • Dependencies: None (pure Python)

Core Imports

import exifread

For specific components:

from exifread import process_file
from exifread.core.exceptions import ExifError, InvalidExif, ExifNotFound
from exifread.core.ifd_tag import IfdTag
from exifread.utils import get_gps_coords, Ratio
from exifread.serialize import convert_types

Architecture

ExifRead uses a multi-layered architecture for EXIF metadata extraction:

  1. File Format Detection: Automatically identifies image format (JPEG, TIFF, PNG, WebP, HEIC, etc.) and locates EXIF data blocks within the file structure
  2. IFD Processing: Parses Image File Directory (IFD) structures that contain organized metadata entries with tags, types, and values
  3. Tag Extraction: Converts raw binary EXIF data into structured IfdTag objects with human-readable information and typed values
  4. Type Conversion: Optionally converts IfdTag objects to standard Python types (int, float, str, list) for easier serialization and programmatic use

The core process_file() function orchestrates this pipeline with configurable options for performance optimization (quick mode, early termination) and output format control (builtin types, thumbnail extraction). The architecture supports both lenient processing (warnings for errors) and strict processing (exceptions on errors) to accommodate various use cases from casual metadata viewing to production data processing.

Basic Usage

import exifread

# Basic EXIF extraction
with open("image.jpg", "rb") as f:
    tags = exifread.process_file(f)

# Iterate through tags (skip long/boring ones)
for tag, value in tags.items():
    if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', 'EXIF MakerNote'):
        print(f"Key: {tag}, value: {value}")

# Quick processing (skip MakerNotes and thumbnails)
with open("image.jpg", "rb") as f:
    tags = exifread.process_file(f, details=False, extract_thumbnail=False)

# Convert to built-in Python types for JSON serialization
with open("image.jpg", "rb") as f:
    tags = exifread.process_file(f, builtin_types=True)
    import json
    json_data = json.dumps(tags)

# Extract GPS coordinates
from exifread.utils import get_gps_coords
coords = get_gps_coords(tags)  # Returns (latitude, longitude) or None
if coords:
    lat, lng = coords
    print(f"GPS: {lat}, {lng}")

Capabilities

EXIF Processing

Extract comprehensive EXIF metadata from digital image files with configurable processing options for performance optimization and data format conversion.

def process_file(
    fh,
    stop_tag="UNDEF",
    details=True,
    strict=False,
    debug=False,
    truncate_tags=True,
    auto_seek=True,
    extract_thumbnail=True,
    builtin_types=False
) -> dict:
    """
    Process an image file to extract EXIF metadata.

    Parameters:
    - fh: Binary file handle opened in 'rb' mode
    - stop_tag: Stop processing when this tag is retrieved (default: "UNDEF")
    - details: If True, process MakerNotes for camera-specific metadata (default: True)
    - strict: If True, raise exceptions on errors instead of logging warnings (default: False)
    - debug: Output detailed debug information during processing (default: False)
    - truncate_tags: If True, truncate printable tag output for readability (default: True)
    - auto_seek: If True, automatically seek to start of file (default: True)
    - extract_thumbnail: If True, extract JPEG thumbnail if present (default: True)
    - builtin_types: If True, convert tags to standard Python types for serialization (default: False)

    Returns:
    Dictionary mapping tag names to values. Keys are formatted as "IFD_NAME TAG_NAME".
    Values are IfdTag objects (builtin_types=False) or Python built-in types (builtin_types=True).
    
    Raises:
    - InvalidExif: When EXIF data is malformed (strict=True only)
    - ExifNotFound: When no EXIF data is found (strict=True only)
    """

GPS Coordinate Extraction

Extract latitude and longitude coordinates from EXIF GPS tags with automatic conversion to decimal degrees format.

def get_gps_coords(tags: dict) -> tuple[float, float] | None:
    """
    Extract GPS coordinates from EXIF tags.

    Parameters:
    - tags: Dictionary of EXIF tags from process_file()

    Returns:
    Tuple of (latitude, longitude) in decimal degrees, or None if GPS data not found.
    Handles both IfdTag objects and serialized tags.
    """

Data Serialization

Convert EXIF tag objects to standard Python types for easier programmatic use and JSON serialization.

def convert_types(exif_tags: dict) -> dict:
    """
    Convert Exif IfdTags to built-in Python types.

    Parameters:
    - exif_tags: Dictionary of IfdTag objects from process_file()

    Returns:
    Dictionary with same keys but values converted to int, float, str, bytes, list, or None.
    Single-element lists are unpacked to individual values.
    """

Command Line Interface

Process image files from the command line with various output and processing options.

def main() -> None:
    """Main CLI entry point. Processes command line arguments and extracts EXIF data."""

def run_cli(args: argparse.Namespace) -> None:
    """
    Execute CLI processing with parsed arguments.
    
    Parameters:
    - args: Parsed command line arguments from get_args()
    """

def get_args() -> argparse.Namespace:
    """
    Parse command line arguments.
    
    Returns:
    argparse.Namespace with parsed CLI options
    """

CLI Usage:

# Basic usage
EXIF.py image.jpg

# Multiple files with quick processing and builtin types
EXIF.py -q -b image1.jpg image2.tiff

# Stop at specific tag with strict error handling
EXIF.py -t DateTimeOriginal -s image.jpg

# Debug mode with color output
EXIF.py -d -c image.jpg

# Module execution
python -m exifread image.jpg

CLI Options:

  • -v, --version: Display version information
  • -q, --quick: Skip MakerNotes and thumbnails for faster processing
  • -t TAG, --tag TAG: Stop processing when specified tag is retrieved
  • -s, --strict: Run in strict mode (raise exceptions on errors)
  • -b, --builtin: Convert IfdTag values to built-in Python types
  • -d, --debug: Run in debug mode with detailed information
  • -c, --color: Enable colored output (POSIX systems only)

Logging

Configure logging output with debug information and colored formatting for development and troubleshooting.

def get_logger():
    """
    Get the exifread logger instance.
    
    Returns:
    logging.Logger configured for exifread
    """

def setup_logger(debug: bool, color: bool) -> None:
    """
    Configure logger with debug and color settings.
    
    Parameters:
    - debug: Enable debug level logging
    - color: Enable colored console output
    """

Data Types

Tag Representation

class IfdTag:
    """
    Represents an IFD (Image File Directory) tag containing EXIF metadata.
    
    Attributes:
    - printable: Human-readable version of the tag data (str)
    - tag: Numeric tag identifier (int)
    - field_type: EXIF field type from FieldType enum
    - values: Raw tag values (various types: str, bytes, list)
    - field_offset: Byte offset of field start in IFD (int)
    - field_length: Length of data field in bytes (int)
    - prefer_printable: Whether to prefer printable over raw values for serialization (bool)
    """
    
    def __init__(
        self,
        printable: str,
        tag: int,
        field_type: FieldType,
        values,
        field_offset: int,
        field_length: int,
        prefer_printable: bool = True
    ) -> None: ...
    
    def __str__(self) -> str:
        """Return printable representation of tag."""
    
    def __repr__(self) -> str:
        """Return detailed representation with field type and offset information."""

Ratio Values

class Ratio:
    """
    Represents EXIF ratio values, extending fractions.Fraction with additional methods.
    Used for GPS coordinates, exposure times, and other fractional EXIF data.
    """
    
    def __new__(cls, numerator: int = 0, denominator: int | None = None): ...
    
    @property
    def num(self) -> int:
        """Get numerator value."""
    
    @property  
    def den(self) -> int:
        """Get denominator value."""
    
    def decimal(self) -> float:
        """Convert ratio to decimal floating point value."""

Field Types

class FieldType:
    """
    EXIF field type enumeration defining data storage formats.
    """
    PROPRIETARY = 0
    BYTE = 1
    ASCII = 2
    SHORT = 3
    LONG = 4
    RATIO = 5
    SIGNED_BYTE = 6
    UNDEFINED = 7
    SIGNED_SHORT = 8
    SIGNED_LONG = 9
    SIGNED_RATIO = 10
    FLOAT_32 = 11
    FLOAT_64 = 12
    IFD = 13

Exception Types

class ExifError(Exception):
    """Base exception class for all ExifRead errors."""

class InvalidExif(ExifError):
    """Raised when EXIF data is malformed or corrupted."""

class ExifNotFound(ExifError):
    """Raised when no EXIF data is found in the image file."""

Constants

__version__: str = "3.5.1"
"""Package version string."""

DEFAULT_STOP_TAG: str = "UNDEF"
"""Default tag name to stop processing at."""

IGNORE_TAGS: list[int] = [0x02BC, 0x927C, 0x9286]
"""Tag IDs ignored during quick processing mode."""

FIELD_DEFINITIONS: dict
"""Maps FieldType values to (byte_length, description) tuples."""

Usage Examples

Processing Multiple Images

import exifread
import os
from pathlib import Path

def process_directory(directory_path):
    """Process all JPEG images in a directory."""
    image_extensions = {'.jpg', '.jpeg', '.tiff', '.tif'}
    
    for file_path in Path(directory_path).iterdir():
        if file_path.suffix.lower() in image_extensions:
            try:
                with open(file_path, 'rb') as f:
                    tags = exifread.process_file(f, builtin_types=True)
                    
                if tags:
                    print(f"\n{file_path.name}:")
                    # Print camera info
                    camera = tags.get('Image Make', 'Unknown')
                    model = tags.get('Image Model', 'Unknown')
                    print(f"  Camera: {camera} {model}")
                    
                    # Print capture date
                    date_taken = tags.get('EXIF DateTimeOriginal', 'Unknown')
                    print(f"  Date: {date_taken}")
                    
                    # Print GPS if available
                    from exifread.utils import get_gps_coords
                    coords = get_gps_coords(tags)
                    if coords:
                        lat, lng = coords
                        print(f"  GPS: {lat:.6f}, {lng:.6f}")
                        
            except Exception as e:
                print(f"Error processing {file_path.name}: {e}")

# Usage
process_directory("/path/to/photos")

Custom Tag Processing

import exifread

def extract_specific_tags(file_path, target_tags):
    """Extract only specific EXIF tags efficiently."""
    
    with open(file_path, 'rb') as f:
        # Use quick mode and stop early for efficiency
        tags = exifread.process_file(
            f, 
            details=False,           # Skip MakerNotes
            extract_thumbnail=False, # Skip thumbnails
            builtin_types=True      # Get Python types
        )
    
    result = {}
    for tag_name in target_tags:
        if tag_name in tags:
            result[tag_name] = tags[tag_name]
    
    return result

# Extract specific metadata
important_tags = [
    'Image Make',
    'Image Model', 
    'EXIF DateTimeOriginal',
    'EXIF ExposureTime',
    'EXIF FNumber',
    'EXIF ISOSpeedRatings'
]

metadata = extract_specific_tags('photo.jpg', important_tags)
print(metadata)

Image Orientation Correction

import exifread
from PIL import Image

def correct_image_orientation(image_path):
    """Correct image orientation based on EXIF data."""
    
    # Read EXIF orientation
    with open(image_path, 'rb') as f:
        tags = exifread.process_file(f, details=False)
    
    # Open image
    img = Image.open(image_path)
    
    # Check for orientation tag
    orientation = tags.get('Image Orientation')
    if orientation:
        orientation_value = orientation.values[0] if hasattr(orientation, 'values') else orientation
        
        # Apply rotation based on orientation
        if orientation_value == 3:
            img = img.transpose(Image.ROTATE_180)
        elif orientation_value == 6:
            img = img.transpose(Image.ROTATE_270)
        elif orientation_value == 8:
            img = img.transpose(Image.ROTATE_90)
    
    return img

# Usage
corrected_image = correct_image_orientation('photo.jpg')
corrected_image.save('corrected_photo.jpg')

Error Handling

The library provides different error handling approaches based on the strict parameter:

Lenient Mode (default):

# Warnings logged, empty dict returned on errors
with open('corrupted.jpg', 'rb') as f:
    tags = exifread.process_file(f)  # Returns {} if no EXIF found

Strict Mode:

from exifread.core.exceptions import ExifNotFound, InvalidExif

try:
    with open('image.jpg', 'rb') as f:
        tags = exifread.process_file(f, strict=True)
except ExifNotFound:
    print("No EXIF data found in image")
except InvalidExif:
    print("EXIF data is corrupted or invalid")
except Exception as e:
    print(f"Unexpected error: {e}")

Supported Image Formats

  • TIFF: Tagged Image File Format with full EXIF support
  • JPEG: JPEG images with EXIF metadata blocks
  • JPEG XL: Next-generation JPEG format
  • PNG: Portable Network Graphics with EXIF chunks
  • WebP: Web-optimized format with EXIF support
  • HEIC: High Efficiency Image Container (Apple format)
  • RAW: Various camera RAW formats with embedded EXIF

Performance Considerations

Quick Processing: Use details=False and extract_thumbnail=False for faster processing:

tags = exifread.process_file(f, details=False, extract_thumbnail=False)

Early Termination: Stop processing at specific tags:

tags = exifread.process_file(f, stop_tag='DateTimeOriginal')

Memory Efficiency: Use built-in types to avoid keeping IfdTag objects:

tags = exifread.process_file(f, builtin_types=True)