Install imagemeta
masterInstall the imagemeta library using go get to start extracting EXIF and XMP metadata from images and camera RAW formats.
go get github.com/evanoberholster/imagemetarepository·master·Indexed 41 days ago
https://github.com/evanoberholster/imagemetaA 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.
Install the imagemeta library using go get to start extracting EXIF and XMP metadata from images and camera RAW formats.
go get github.com/evanoberholster/imagemetaTo 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)
}The github.com/evanoberholster/imagemeta/meta/xmp package allows you to parse XMP data from two sources:
xmp.ParseXmp(file) to parse a standalone .xmp file.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)If you need to identify a file format before performing a full decode, use the imagetype package. The Scan function provides fast format detection.
import "github.com/evanoberholster/imagemeta/imagetype"
t, err := imagetype.Scan(r)imagehash package provides low-allocation perceptual hashing for images. It supports both 64-bit and 256-bit hashing algorithms.The imagemeta package provides several specialized decoding entry points for specific file formats. All methods accept an io.ReadSeeker:
Decode(io.ReadSeeker): General purpose decoderDecodeJPEG(io.ReadSeeker)DecodeTiff(io.ReadSeeker)DecodeCR3(io.ReadSeeker)DecodeCRW(io.ReadSeeker)DecodeHeif(io.ReadSeeker)DecodePng(io.ReadSeeker)PreviewCR3(io.ReadSeeker): Extracts embedded CR3 previewsUse 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())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...)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)
}
}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.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:
0xff 0xd8) before returning the data.maxLength in PreviewOption, it returns ErrNoPreview.// 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
}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)