imagemeta

package module
v0.8.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 4, 2025 License: MIT Imports: 23 Imported by: 3

README

Tests on Linux, MacOS and Windows Go Report Card codecov GoDoc

This is about READING image metadata

Writing is not supported, and never will.

I welcome PRs with fixes, but please raise an issue first if you want to add new features.

Performance

Extracting EXIF performs well, ref. the benhcmark below. Note that you can get a significant boost if you only need a subset of the fields (e.g. only the Orientation). The last line is with the library that Hugo used before it was replaced with this.

BenchmarkDecodeCompareWithGoexif/bep/imagemeta/exif/jpeg/alltags-10                62474             19054 ns/op            4218 B/op        188 allocs/op
BenchmarkDecodeCompareWithGoexif/bep/imagemeta/exif/jpeg/orientation-10           309145              3723 ns/op             352 B/op          8 allocs/op
BenchmarkDecodeCompareWithGoexif/rwcarlsen/goexif/exif/jpg/alltags-10              21987             50195 ns/op          175548 B/op        812 allocs/op

Looking at some more extensive tests, testing different image formats and tag sources, we see that the current XMP implementation leaves a lot to be desired (you can provide your own XMP handler if you want).

BenchmarkDecode/png/exif-10                39164             30783 ns/op            4231 B/op        189 allocs/op
BenchmarkDecode/png/all-10                  5617            206111 ns/op           48611 B/op        310 allocs/op
BenchmarkDecode/webp/all-10                 3069            379637 ns/op          144181 B/op       2450 allocs/op
BenchmarkDecode/webp/xmp-10                 3291            359133 ns/op          139991 B/op       2265 allocs/op
BenchmarkDecode/webp/exif-10               47028             25788 ns/op            4255 B/op        191 allocs/op
BenchmarkDecode/jpg/exif-10                58701             20216 ns/op            4223 B/op        188 allocs/op
BenchmarkDecode/jpg/iptc-10               135777              8725 ns/op            1562 B/op         80 allocs/op
BenchmarkDecode/jpg/iptc/category-10      215674              5393 ns/op             456 B/op         15 allocs/op
BenchmarkDecode/jpg/iptc/city-10          192067              6201 ns/op             553 B/op         17 allocs/op
BenchmarkDecode/jpg/xmp-10                  3244            359436 ns/op          139861 B/op       2263 allocs/op
BenchmarkDecode/jpg/all-10                  2874            389489 ns/op          145700 B/op       2523 allocs/op
BenchmarkDecode/tiff/exif-10                2065            566786 ns/op          214089 B/op        282 allocs/op
BenchmarkDecode/tiff/iptc-10               16761             71003 ns/op            2603 B/op        133 allocs/op
BenchmarkDecode/tiff/all-10                 1267            933321 ns/op          356878 B/op       2668 allocs/op

When in doubt, Exiftool is right

The output of this library is tested against exiftool -n -json. This means, for example, that:

  • We use f-numbers and not APEX for aperture values.
  • We use seconds and not APEX for shutter speed values.
  • EXIF field definitions are fetched from this table: https://exiftool.org/TagNames/EXIF.html
  • IPTC field definitions are fetched from this table: https://exiftool.org/TagNames/IPTC.html
  • The XMP handling is currently very simple, you can supply your own XMP handler (see the HandleXMP option) if you need more.

There are some subtle differences in output:

  • Exiftool prints rationale number arrays as space formatted strings with a format/precision that seems unnecessary hard to replicate, so we use strconv.FormatFloat(f, 'f', -1, 64) for these.

Development

Many of the tests depends on generated golden files. To update these, run:

 go generate ./gen

Note that you need a working exiftool and updated binary in your PATH for this to work. This was tested OK with:

exiftool -ver
12.76

Debuggin tips:

 exiftool testdata/goexif_samples/has-lens-info.jpg -htmldump > dump.html

Documentation

Index

Constants

View Source
const UnknownPrefix = "UnknownTag_"

UnknownPrefix is used as prefix for unknown tags.

Variables

View Source
var (
	// ErrStopWalking is a sentinel error to signal that the walk should stop.
	ErrStopWalking = fmt.Errorf("stop walking")
)

Functions

func Decode

func Decode(opts Options) (err error)

Decode reads EXIF and IPTC metadata from r and returns a Meta struct.

func IsInvalidFormat

func IsInvalidFormat(err error) bool

IsInvalidFormat reports whether the error was an InvalidFormatError.

Types

type HandleTagFunc

type HandleTagFunc func(info TagInfo) error

HandleTagFunc is the function that is called for each tag.

type ImageFormat

type ImageFormat int

ImageFormat is the image format.

const (
	// ImageFormatAuto signals that the image format should be detected automatically (not implemented yet).
	ImageFormatAuto ImageFormat = iota
	// JPEG is the JPEG image format.
	JPEG
	// TIFF is the TIFF image format.
	TIFF
	// PNG is the PNG image format.
	PNG
	// WebP is the WebP image format.
	WebP
)

func (ImageFormat) String

func (i ImageFormat) String() string

type InvalidFormatError

type InvalidFormatError struct {
	Err error
}

InvalidFormatError is used when the format is invalid.

func (*InvalidFormatError) Error

func (e *InvalidFormatError) Error() string

func (*InvalidFormatError) Is added in v0.6.0

func (e *InvalidFormatError) Is(target error) bool

Is reports whether the target error is an InvalidFormatError.

type Options

type Options struct {
	// The Reader (typically a *os.File) to read image metadata from.
	R io.ReadSeeker

	// The image format in R.
	ImageFormat ImageFormat

	// If set, the decoder skip tags in which this function returns false.
	// If not set, a default function is used that skips all EXIF tags except those in IFD0.
	ShouldHandleTag func(tag TagInfo) bool

	// The function to call for each tag.
	HandleTag HandleTagFunc

	// The default XMP handler is currently very simple:
	// It decodes the RDF.Description.Attrs using Go's xml package and passes each tag to HandleTag.
	// If HandleXMP is set, the decoder will call this function for each XMP packet instead.
	// Note that r must be read completely.
	HandleXMP func(r io.Reader) error

	// If set, the decoder will only read the given tag sources.
	// Note that this is a bitmask and you may send multiple sources at once.
	Sources Source

	// Warnf will be called for each warning.
	Warnf func(string, ...any)

	// Timeout is the maximum time the decoder will spend on reading metadata.
	// Mostly useful for testing.
	// If set to 0, the decoder will not time out.
	Timeout time.Duration
}

Options contains the options for the Decode function.

type Rat

type Rat[T int32 | uint32] interface {
	Num() T
	Den() T
	Float64() float64

	// String returns the string representation of the rational number.
	// If the denominator is 1, the string will be the numerator only.
	String() string
}

Rat is a rational number.

func NewRat

func NewRat[T int32 | uint32](num, den T) (Rat[T], error)

NewRat returns a new Rat with the given numerator and denominator.

type Source added in v0.5.0

type Source uint32

Source is a bitmask and you may send multiple sources at once.

const (
	// EXIF is the EXIF tag source.
	EXIF Source = 1 << iota
	// IPTC is the IPTC tag source.
	IPTC
	// XMP is the XMP tag source.
	XMP
)

func (Source) Has added in v0.5.0

func (t Source) Has(source Source) bool

Has returns true if the given source is set.

func (Source) IsZero added in v0.5.0

func (t Source) IsZero() bool

IsZero returns true if the source is zero.

func (Source) Remove added in v0.5.0

func (t Source) Remove(source Source) Source

Remove removes the given source.

func (Source) String added in v0.5.0

func (i Source) String() string

type TagInfo

type TagInfo struct {
	// The tag source.
	Source Source
	// The tag name.
	Tag string
	// The tag namespace.
	// For EXIF, this is the path to the IFD, e.g. "IFD0/GPSInfoIFD"
	// For XMP, this is the namespace, e.g. "http://ns.adobe.com/camera-raw-settings/1.0/"
	// For IPTC, this is the record tag name as defined https://exiftool.org/TagNames/IPTC.html
	Namespace string
	// The tag value.
	Value any
}

TagInfo contains information about a tag.

type Tags

type Tags struct {
	// contains filtered or unexported fields
}

Tags is a collection of tags grouped per source.

func (*Tags) Add

func (t *Tags) Add(tag TagInfo)

Add adds a tag to the correct source.

func (Tags) All

func (t Tags) All() map[string]TagInfo

All returns all tags in a map.

func (*Tags) EXIF

func (t *Tags) EXIF() map[string]TagInfo

EXIF returns the EXIF tags.

func (Tags) GetDateTime

func (t Tags) GetDateTime() (time.Time, error)

GetDateTime tries DateTimeOriginal and then DateTime, in the EXIF tags, and returns the parsed time.Time value if found.

func (Tags) GetLatLong

func (t Tags) GetLatLong() (lat float64, long float64, err error)

GetLatLong returns the latitude and longitude from the EXIF GPS tags.

func (*Tags) Has

func (t *Tags) Has(tag TagInfo) bool

Has reports if a tag is already added.

func (*Tags) IPTC

func (t *Tags) IPTC() map[string]TagInfo

IPTC returns the IPTC tags.

func (*Tags) XMP

func (t *Tags) XMP() map[string]TagInfo

XMP returns the XMP tags.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL