imagemeta

repository·master·Indexed 41 days ago

https://github.com/evanoberholster/imagemeta

A high-performance Go library for extracting EXIF and XMP metadata from images and camera RAW formats. It features low-allocation parsing and ExifTool-aligned behavior, supporting formats such as JPEG, TIFF, PNG, HEIF, and Canon CR3/CRW, Sony ARW, and DNG. The library includes specialized tools for parsing XMP sidecars, detecting image types, generating perceptual image hashes, and extracting embedded JPEG previews from TIFF-based raw files.

Tokens
4K
Snippets
19
Records
23
Agent score
52%

What's inside imagemeta

  1. Quick Start: Extract metadata from an image

    master

    To extract basic metadata like image type, camera make, model, and original date/time, use the imagemeta.Decode function. This function accepts an io.ReadSeeker (such as an *os.File).

    package main
    
    import (
    	"fmt"
    	"os"
    
    	"github.com/evanoberholster/imagemeta"
    )
    
    func main() {
    	f, err := os.Open("image.jpg")
    	if err != nil {
    		panic(err)
    	}
    	defer f.Close()
    
    	ex, err := imagemeta.Decode(f)
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println("type:", ex.ImageType)
    	fmt.Println("make:", ex.Make)
    	fmt.Println("model:", ex.Model)
    	fmt.Println("datetime:", ex.DateTimeOriginal)
    }
  2. Parse XMP metadata

    master

    The github.com/evanoberholster/imagemeta/meta/xmp package allows you to parse XMP data from two sources:

    1. Sidecar files: Use xmp.ParseXmp(file) to parse a standalone .xmp file.
    2. Embedded blocks: Use xmp.Parse(file) to extract XMP data embedded directly within an image file.
    // Parse a sidecar .xmp file
    x, err := xmp.ParseXmp(file)
    
    // Parse embedded XMP from an image
    x, err := xmp.Parse(file)
  3. Use the Decode API

    master

    The imagemeta package provides several specialized decoding entry points for specific file formats. All methods accept an io.ReadSeeker:

    • Decode(io.ReadSeeker): General purpose decoder
    • DecodeJPEG(io.ReadSeeker)
    • DecodeTiff(io.ReadSeeker)
    • DecodeCR3(io.ReadSeeker)
    • DecodeCRW(io.ReadSeeker)
    • DecodeHeif(io.ReadSeeker)
    • DecodePng(io.ReadSeeker)
    • PreviewCR3(io.ReadSeeker): Extracts embedded CR3 previews
  4. Get the name and size of an EXIF tag

    master

    Use the following methods to get human-readable or structural information about an Entry:

    • Name() string: Returns the string name of the tag based on its ID and IfdType (e.g., "ImageWidth").
    • Size() uint32: Returns the total size in bytes of the tag's value (Type.Size() * UnitCount).
    fmt.Printf("Tag: %s, Size: %d bytes\n", entry.Name(), entry.Size())
  5. Configure preview extraction limits with PreviewOption

    master

    You can pass PreviewOption functions to TIFFPreviews, ExtractPreview, or PreviewTIFF to control extraction behavior.

    Currently, the primary option is:

    • WithMaxPreviewLength(n int64): Sets a maximum byte length for previews. If a preview's declared length is greater than n, it will be ignored by TIFFPreviews or cause ExtractPreview to return ErrNoPreview. This is useful for preventing large, potentially malicious allocations based on attacker-controlled metadata.
    // Limit previews to 5MB to prevent large memory allocations
    opts := []imagemeta.PreviewOption{imagemeta.WithMaxPreviewLength(5 * 1024 * 1024)}
    
    previews, err := imagemeta.TIFFPreviews(file, opts...)
  6. Get the largest embedded preview with PreviewTIFF()

    master

    Use PreviewTIFF(r io.ReadSeeker, opts ...PreviewOption) ([]byte, error) as a convenience method to quickly get the largest renderable JPEG preview from a TIFF-based file.

    This function internally calls TIFFPreviews and attempts to ExtractPreview each one in order until it succeeds. If no valid preview can be extracted, it returns ErrNoPreview.

    // Quickly get the best available preview
    previewBytes, err := imagemeta.PreviewTIFF(file)
    if err != nil {
        if errors.Is(err, imagemeta.ErrNoPreview) {
            fmt.Println("No valid preview found")
        } else {
            log.Fatal(err)
        }
    }
  7. Decode specific image formats

    master

    While Decode() handles automatic detection, you can use format-specific functions if you already know the image type. These functions return an exif.Exif object or an error.

    Supported specific decoders:

    • DecodeCR3(r io.ReadSeeker): Decodes Canon CR3 files.
    • DecodeTiff(r io.ReadSeeker): Decodes TIFF or DNG files.
    • DecodeCR2(r io.ReadSeeker): Decodes Canon CR2 files (uses DecodeTiff).
    • DecodeARW(r io.ReadSeeker): Decodes Sony ARW files (uses DecodeTiff).
    • DecodeCRW(r io.ReadSeeker): Decodes Canon CRW files.
    • DecodeHeif(r io.ReadSeeker): Decodes HEIF files.
    • DecodeJPEG(r io.ReadSeeker): Decodes JPEG files.
    • DecodePng(r io.ReadSeeker): Decodes PNG files.
  8. Extract a specific preview image with ExtractPreview()

    master

    Use ExtractPreview(r io.ReadSeeker, p PreviewImage, opts ...PreviewOption) ([]byte, error) to read the raw bytes of a specific preview identified by a PreviewImage struct.

    Important details:

    • The function validates the JPEG SOI marker (0xff 0xd8) before returning the data.
    • It performs bounds checking against the actual file size to prevent out-of-bounds reads.
    • If the preview exceeds the configured maxLength in PreviewOption, it returns ErrNoPreview.
    • Returns a newly allocated byte slice containing the JPEG data.
    // Extract the first (largest) preview found
    if len(previews) > 0 {
        data, err := imagemeta.ExtractPreview(file, previews[0])
        if err != nil {
            if errors.Is(err, imagemeta.ErrNoPreview) {
                // Handle no preview
            }
            log.Fatal(err)
        }
        // 'data' now contains the JPEG bytes
    }
  9. Create an Entry with NewEntry() or NewInlineEntry()

    master

    Use NewEntry for standard TIFF entries where the value is stored at a file offset. Use NewInlineEntry for BigTIFF entries where the value is packed inline within the 8-byte value field. NewInlineEntry automatically sets the embedded flag to true, signaling to parsers that ValueOffset should be treated as data rather than a file pointer.

    // Standard entry
    entry := tag.NewEntry(id, typ, unitCount, valueOffset, directoryType, ifdIndex, byteOrder)
    
    // Inline entry (BigTIFF)
    entry := tag.NewInlineEntry(id, typ, unitCount, lo, hi, directoryType, ifdIndex, byteOrder)