gometadata

package module
v1.0.4 Latest Latest
Warning

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

Go to latest
Published: Apr 8, 2026 License: MIT Imports: 22 Imported by: 0

README

GoMetadata

GoMetadata

Go Reference Go Report Card CI codecov OpenSSF Scorecard Release

A pure Go library for reading and writing EXIF, IPTC, and XMP metadata from any image format. GoMetadata provides a single, unified API over all three metadata standards — EXIF 3.0 (CIPA DC-008 / TIFF 6.0), IPTC IIM 4.2, and XMP (ISO 16684-1) — across 13 container formats including JPEG, TIFF, PNG, WebP, HEIF/AVIF, and the major RAW formats (CR2, CR3, NEF, ARW, DNG, ORF, RW2).

Developers searching for a Go EXIF library, a Go IPTC parser, or a way to read and write XMP metadata in Go will find that GoMetadata handles all three in a single import. Format detection is by magic bytes, not file extension. All parsers are fuzz-tested and race-clean.

Installation

go get github.com/FlavioCFOliveira/GoMetadata

Requires Go 1.26 or later. No non-stdlib runtime dependencies.

Usage

Reading common fields
package main

import (
	"fmt"
	"log"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("photo.jpg")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Camera:", m.CameraModel())
	fmt.Println("Make:  ", m.Make())
	fmt.Println("Lens:  ", m.LensModel())

	if lat, lon, ok := m.GPS(); ok {
		fmt.Printf("GPS: %.6f, %.6f\n", lat, lon)
	}
	if t, ok := m.DateTimeOriginal(); ok {
		fmt.Println("Shot:", t)
	}
	if num, den, ok := m.ExposureTime(); ok {
		fmt.Printf("Exposure: %d/%d s\n", num, den)
	}
	if f, ok := m.FNumber(); ok {
		fmt.Printf("Aperture: f/%.1f\n", f)
	}
	if iso, ok := m.ISO(); ok {
		fmt.Println("ISO:", iso)
	}

	fmt.Println("Caption:  ", m.Caption())
	fmt.Println("Copyright:", m.Copyright())
	fmt.Println("Keywords: ", m.Keywords())
}
Writing and modifying metadata

Write and WriteFile preserve all image data and all metadata not explicitly changed. WriteFile performs an atomic in-place update via a temporary file and rename.

package main

import (
	"log"
	"time"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("photo.jpg")
	if err != nil {
		log.Fatal(err)
	}

	m.SetCaption("Grand Canyon, South Rim")
	m.SetCopyright("2024 Jane Smith")
	m.SetCreator("Jane Smith")
	m.SetKeywords([]string{"landscape", "canyon", "arizona"})
	m.SetGPS(36.0544, -112.1401)
	m.SetDateTimeOriginal(time.Date(2024, 9, 14, 7, 32, 0, 0, time.UTC))

	if err := gometadata.WriteFile("photo.jpg", m); err != nil {
		log.Fatal(err)
	}
}
Skipping segments for faster reads

Use ReadOption helpers to skip segments you do not need. Skipping the MakerNote is the single biggest speed win for cameras with large proprietary blocks.

m, err := gometadata.ReadFile("photo.jpg",
	gometadata.WithoutMakerNote(),
	gometadata.WithoutIPTC(),
	gometadata.WithoutXMP(),
)
Raw segment access and building from scratch

When you need direct access to the raw bytes of a segment, or want to construct a Metadata value to embed in a new file:

// Raw segment bytes — useful for forwarding to another library or logging.
exifBytes := m.RawEXIF()
xmpBytes  := m.RawXMP()
iptcBytes := m.RawIPTC()

// Build a Metadata value from scratch (no source image required).
import "github.com/FlavioCFOliveira/GoMetadata/format"

blank := gometadata.NewMetadata(format.JPEG)
blank.SetCameraModel("Custom Device")
blank.SetCopyright("2024 Example Corp")

Examples

Reading RAW file metadata

examples/raw-inspector — extract camera identification, shooting parameters, GPS, and descriptive fields from any RAW format (CR2, CR3, NEF, ARW, DNG, ORF, RW2). Format is detected automatically from magic bytes.

// WithoutMakerNote skips the costliest part of EXIF parsing —
// the manufacturer-specific IFD — when only standard tags are needed.
m, err := gometadata.ReadFile(path, gometadata.WithoutMakerNote())

fmt.Printf("Format:      %s\n",     m.Format())
fmt.Printf("Make/Model:  %s %s\n",  m.Make(), m.CameraModel())
fmt.Printf("Lens:        %s\n",     m.LensModel())

if num, den, ok := m.ExposureTime(); ok { fmt.Printf("Shutter: 1/%d s\n", den/num) }
if f, ok := m.FNumber();             ok { fmt.Printf("Aperture: f/%.1f\n", f) }
if iso, ok := m.ISO();               ok { fmt.Printf("ISO: %d\n", iso) }
if fl, ok := m.FocalLength();        ok { fmt.Printf("Focal: %.0f mm\n", fl) }
if wb, ok := m.WhiteBalance();       ok { fmt.Printf("WB: %d\n", wb) }   // 0=auto 1=manual
if fl, ok := m.Flash();              ok { fmt.Printf("Flash fired: %v\n", fl&0x01 != 0) }
if lat, lon, ok := m.GPS();          ok { fmt.Printf("GPS: %.6f, %.6f\n", lat, lon) }
if alt, ok := m.Altitude();          ok { fmt.Printf("Altitude: %.1f m\n", alt) }

examples/copyright-stamp — walk a directory tree and embed copyright, creator, caption, and keywords into every image. Setters write to all non-nil metadata components (EXIF, IPTC, XMP) in one call.

m, err := gometadata.ReadFile(path)
// Distinguish corrupt / truncated files from hard I/O errors.
var corrupt *gometadata.CorruptMetadataError
var truncated *gometadata.TruncatedFileError
switch {
case errors.As(err, &corrupt):   /* skip */
case errors.As(err, &truncated): /* skip */
}

// Each setter writes to all non-nil metadata layers simultaneously:
// SetCopyright → EXIF tag 0x8298 + IPTC dataset 2:116 + XMP dc:rights
m.SetCopyright("© 2025 Jane Smith. All rights reserved.")
m.SetCreator("Jane Smith")
m.SetCaption("Grand Canyon at sunset")
m.SetKeywords([]string{"landscape", "canyon", "arizona"})

gometadata.WriteFile(path, m) // atomic: temp file + rename

Stream pipeline (no disk I/O)

examples/stream-transcode — read metadata from stdin, update fields, write to stdout. No temporary files. Works with any io.ReadSeeker and io.Writernet/http, bytes.Buffer, object-store streams.

m, err := gometadata.Read(os.Stdin)  // *os.File implements io.ReadSeeker

m.SetCaption("...")
m.SetGPS(48.8566, 2.3522) // Paris

// Seek back so Write can re-read the original image bytes from the same handle.
os.Stdin.Seek(0, io.SeekStart)

// PreserveUnknownSegments passes APP/chunk segments the library
// does not recognise through byte-for-byte (e.g. ICC profiles).
gometadata.Write(os.Stdin, os.Stdout, m, gometadata.PreserveUnknownSegments(true))
stream-transcode -caption "Night shot" -copyright "2025 J. Smith" < input.jpg > output.jpg

JSON metadata export

examples/gallery-sidecar — parse images and emit a JSON array for static site generators, search indexes, or API responses. Optional fields use Go pointer types so absent values serialise as null.

type imageRecord struct {
    File        string   `json:"file"`
    Format      string   `json:"format"`
    Model       *string  `json:"model,omitempty"`
    CapturedAt  *string  `json:"captured_at,omitempty"` // RFC3339
    Latitude    *float64 `json:"latitude,omitempty"`
    Longitude   *float64 `json:"longitude,omitempty"`
    ISO         *uint    `json:"iso,omitempty"`
    // ...
}

m, _ := gometadata.ReadFile(path, gometadata.WithoutMakerNote())
rec := imageRecord{File: path, Format: m.Format().String()}
if t, ok := m.DateTimeOriginal(); ok { s := t.Format(time.RFC3339); rec.CapturedAt = &s }
if lat, lon, ok := m.GPS();       ok { rec.Latitude = &lat; rec.Longitude = &lon }
if iso, ok := m.ISO();            ok { rec.ISO = &iso }
gallery-sidecar -pretty photo1.jpg photo2.nef photo3.heic

Multi-format round-trip smoke test

examples/multi-format-roundtrip — read, modify, write, and re-read across all supported formats. Exits non-zero on any mismatch. Useful as a pre-release integration test.

m, _ := gometadata.ReadFile(path)

// Check format before writing — not all container variants support write.
if !format.SupportsWrite(m.Format()) { /* skip */ }

m.SetCaption("roundtrip-test")
m.SetGPS(51.5074, -0.1278)

tmp, _ := os.CreateTemp(filepath.Dir(path), "roundtrip-*"+ext)
defer os.Remove(tmp.Name())

gometadata.WriteFile(tmp.Name(), m)

m2, _ := gometadata.ReadFile(tmp.Name())
fmt.Printf("PASS/FAIL %s (%s): caption=%v\n",
    path, m.Format(), m2.Caption() == "roundtrip-test")

Supported features

Feature Details
Metadata standards EXIF 3.0 (CIPA DC-008 / TIFF 6.0), IPTC IIM 4.2, XMP (ISO 16684-1)
Read All three standards across all 13 container formats
Write All three standards; preserves unmodified metadata byte-for-byte
Atomic writes WriteFile uses temp file + rename — no partial writes
Format detection Magic bytes only; file extension is never consulted
MakerNote (read) Canon, Nikon, Sony, Olympus, Panasonic, Pentax, DJI, FujiFilm, Leica, Samsung, Sigma, Minolta, Casio
Convenience getters 30+ typed getters with explicit source-priority resolution
Convenience setters 15+ setters that write to all applicable non-nil components simultaneously
Priority resolution Each getter documents its source order (e.g., EXIF > XMP); the caller always gets one answer
Lazy parsing WithoutEXIF(), WithoutIPTC(), WithoutXMP(), WithoutMakerNote() skip unwanted work
Allocation budget Zero/near-zero heap allocation in parsing fast paths; sync.Pool for reusable buffers
Fuzz testing 18 fuzz targets covering all parsers
Race safety Clean under go test -race ./...
Corpus coverage 3,000+ real-world images tested, 0 failures

Supported formats

Format Extension(s) Read Write EXIF IPTC XMP
JPEG .jpg, .jpeg Yes Yes Yes Yes Yes
TIFF .tif, .tiff Yes Yes Yes Yes Yes
PNG .png Yes Yes Yes No Yes
WebP .webp Yes Yes Yes No Yes
HEIF .heif, .heic Yes Yes Yes No Yes
AVIF .avif Yes Yes Yes No Yes
Canon CR2 .cr2 Yes Yes Yes No Yes
Canon CR3 .cr3 Yes Yes Yes No Yes
Nikon NEF .nef Yes Yes Yes No Yes
Sony ARW .arw Yes Yes Yes No Yes
Adobe DNG .dng Yes Yes Yes No Yes
Olympus ORF .orf Yes Yes Yes No Yes
Panasonic RW2 .rw2 Yes Yes Yes No Yes

Performance

Benchmarks run with go test -bench=. -benchmem -benchtime=2s ./... (Go 1.26, macOS, GOMAXPROCS=10).
All figures are the mean of multiple runs; allocation counts are stable across runs.

Reproducing the benchmarks

Run the full suite:

go test -bench=. -benchmem -benchtime=2s ./...

Scope to a single package (e.g. the EXIF parser):

go test -bench=. -benchmem -benchtime=2s ./exif/...

Run a single named benchmark:

go test -bench=BenchmarkParseEXIF -benchmem -benchtime=2s ./exif/...

Results vary by machine. The figures in this README were collected on macOS with Go 1.26 and GOMAXPROCS=10. Pin GOMAXPROCS to make comparisons across machines more meaningful:

GOMAXPROCS=10 go test -bench=. -benchmem -benchtime=2s ./...

For lower noise, run multiple iterations and pipe through benchstat:

go install golang.org/x/perf/cmd/benchstat@latest
go test -bench=. -benchmem -benchtime=2s -count=5 ./... | benchstat /dev/stdin

benchstat computes the median and confidence interval across the five runs, which is more reliable than any single measurement.

End-to-end read
Scenario Time/op Memory/op Allocs/op
Progressive JPEG (no metadata) 163 ns 176 B 4
JPEG — EXIF + IPTC + XMP combined 10.6 µs 22.8 kB 24
Real-world JPEG corpus file 1.55 µs 4.7 kB 14
Concurrent reads (parallel goroutines) 11.4 µs 544 B 11
Write
Operation Time/op Memory/op Allocs/op
JPEG — metadata update 282 ns 264 B 15
PNG — pass-through 188 ns 168 B 17
Metadata format parsers
Format Operation Time/op Memory/op Allocs/op
EXIF Parse — minimal TIFF (width, height, orientation) 121 ns 257 B 4
EXIF Parse — camera tags 997 ns 2.4 kB 8
EXIF Encode 121 ns 240 B 6
EXIF IFD tag lookup — 100-entry set (binary search) 3.8 ns 0 B 0
IPTC Parse 102 ns 944 B 2
IPTC Encode 68 ns 96 B 1
IPTC Field accessor 26 ns 64 B 1
XMP Parse 1.06 µs 968 B 12
XMP Encode 650 ns 3.1 kB 2
Container format parsers
Format Operation Time/op
JPEG Segment extraction 102 ns
JPEG Segment injection 206 ns
JPEG Real corpus file (full parse) 2.02 µs
PNG Extraction 192 ns
PNG Extraction — compressed XMP (zlib) 810 ns
TIFF Extraction 98 ns
WebP Extraction 98 ns
HEIF / AVIF Extraction 271 ns
Sony ARW Extraction 81 ns
Canon CR2 Extraction 82 ns
Adobe DNG Extraction 79 ns
Nikon NEF Extraction 80 ns

Canon CR3 and Olympus ORF/Panasonic RW2 benchmarks are covered by the TIFF and BMFF primitive benchmarks; their combined overhead falls within the same 80–100 ns range.

Internal primitives
Component Operation Time/op
sync.Pool buffer Get + Put (≤4 kB) 7.0 ns
sync.Pool buffer Get + Put (>64 kB) 7.2 ns
Byte-order Uint16 little-endian 0.26 ns
Byte-order Uint32 little-endian 0.27 ns
BMFF Read box header 24.8 ns
BMFF Skip box 27.5 ns
RIFF Read chunk header 24.4 ns
Design choices behind the numbers
Technique Effect
sync.Pool-backed buffers (internal/iobuf) Amortises heap allocation to zero after warm-up
Lazy parsing (WithoutEXIF, WithoutIPTC, WithoutXMP, WithoutMakerNote) Skips unwanted segments entirely; MakerNote skip is the largest win on RAW files
Binary search in IFD entry set Tag lookup in a 100-entry IFD costs 3.8 ns and 0 allocations
Lazy map init for extended XMP Map is only allocated when extended XMP is actually present
Magic-byte format detection Dispatch adds no measurable overhead; no string allocation

API reference

Full documentation is available at pkg.go.dev/github.com/FlavioCFOliveira/GoMetadata.

License

MIT

Documentation

Overview

Package gometadata provides a unified API for reading and writing EXIF, IPTC, and XMP metadata from any image format.

Format detection is performed automatically by inspecting magic bytes; file extensions are never used. Supported containers: JPEG, TIFF, PNG, HEIF/HEIC, WebP, and RAW variants (CR2, CR3, NEF, ARW, DNG, ORF, RW2).

Basic usage:

m, err := gometadata.ReadFile("photo.jpg")
if err != nil {
    log.Fatal(err)
}
fmt.Println(m.CameraModel())
lat, lon, ok := m.GPS()

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNilIFD0 = errors.New("gometadata: EXIF struct has nil IFD0; use exif.Parse to construct a valid EXIF")

ErrNilIFD0 is returned when an EXIF struct has a nil IFD0 field.

View Source
var ErrNilIFD0Write = errors.New("gometadata: EXIF struct has nil IFD0")

ErrNilIFD0Write is returned when attempting to write an EXIF struct with a nil IFD0 field.

View Source
var ErrNilXMPProperties = errors.New("gometadata: XMP struct has nil Properties map")

ErrNilXMPProperties is returned when an XMP struct has a nil Properties map.

Functions

func Write

func Write(r io.ReadSeeker, w io.Writer, m *Metadata, opts ...WriteOption) error

Write reads the image from r, applies the metadata in m, and writes the result to w. Image data and unmodified metadata segments are preserved byte-for-byte. r must support seeking (io.ReadSeeker).

Example

ExampleWrite demonstrates writing metadata to an io.Writer. The modified image is written to a bytes.Buffer here, but any io.Writer works — including an os.File or a network connection.

package main

import (
	"bytes"
	"fmt"
	"io"
	"os"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	f, err := os.Open("testdata/corpus/jpeg/exif-samples/11-tests.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer func() { _ = f.Close() }()

	m, err := gometadata.Read(f)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	m.SetCaption("Test caption")

	// Seek back so Write can re-read the image data from the same handle.
	if _, err := f.Seek(0, io.SeekStart); err != nil {
		fmt.Println("error:", err)
		return
	}

	var buf bytes.Buffer
	if err := gometadata.Write(f, &buf, m); err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Printf("output size: %d bytes\n", buf.Len())
}
Output:
output size: 236594 bytes
Example (PreserveUnknownSegments)

ExampleWrite_preserveUnknownSegments demonstrates the PreserveUnknownSegments write option. When set to true (the default), GoMetadata copies APP and chunk segments it does not recognise into the output unchanged — preserving proprietary data such as Photoshop layer info or camera calibration blocks. Set it to false only when you intentionally want to strip unknown segments from the output.

package main

import (
	"bytes"
	"fmt"
	"io"
	"os"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	f, err := os.Open("testdata/corpus/jpeg/exif-samples/11-tests.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer func() { _ = f.Close() }()

	m, err := gometadata.Read(f)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	if _, err := f.Seek(0, io.SeekStart); err != nil {
		fmt.Println("error:", err)
		return
	}

	var buf bytes.Buffer
	if err := gometadata.Write(f, &buf, m, gometadata.PreserveUnknownSegments(true)); err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Printf("output written: %v\n", buf.Len() > 0)
}
Output:
output written: true

func WriteFile

func WriteFile(path string, m *Metadata, opts ...WriteOption) error

WriteFile reads the image at path, applies the metadata in m, and writes the result back to the same file atomically. It is a convenience wrapper around Write.

Example

ExampleWriteFile demonstrates setting metadata on an image and writing it back. The source file is copied to a temp file first so that testdata is never mutated.

package main

import (
	"fmt"
	"io"
	"os"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	const src = "testdata/corpus/jpeg/exif-samples/11-tests.jpg"

	// Copy the source image to a temporary file.
	in, err := os.Open(src)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer func() { _ = in.Close() }()

	tmp, err := os.CreateTemp("", "gometadata-example-*.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer func() {
		_ = tmp.Close()
		_ = os.Remove(tmp.Name())
	}()

	if _, err := io.Copy(tmp, in); err != nil {
		fmt.Println("error:", err)
		return
	}
	_ = tmp.Close()

	// Read the copy, apply metadata changes, and write back.
	m, err := gometadata.ReadFile(tmp.Name())
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	m.SetCaption("Grand Canyon at sunset")
	m.SetCopyright("2024 Jane Smith")
	m.SetCreator("Jane Smith")
	m.SetGPS(36.0544, -112.1401) // Grand Canyon South Rim
	m.SetKeywords([]string{"landscape", "canyon", "sunset"})

	if err := gometadata.WriteFile(tmp.Name(), m); err != nil {
		fmt.Println("error:", err)
		return
	}

	// Verify the round-trip by re-reading the written file.
	m2, err := gometadata.ReadFile(tmp.Name())
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println("Caption:  ", m2.Caption())
	fmt.Println("Copyright:", m2.Copyright())
	fmt.Println("Creator:  ", m2.Creator())
}
Output:
Caption:   Grand Canyon at sunset
Copyright: 2024 Jane Smith
Creator:   Jane Smith
Example (ExposureSettings)

ExampleWriteFile_exposureSettings demonstrates writing the full exposure triangle — shutter speed, aperture, ISO, and focal length — into a JPEG file. SetExposureTime accepts the EXIF RATIONAL representation (numerator/denominator) to preserve fractional shutter speeds exactly without floating-point rounding. All four values are written to EXIF ExifIFD tags 0x829A, 0x829D, 0x8827, and 0x920A respectively.

package main

import (
	"fmt"
	"io"
	"os"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	const src = "testdata/corpus/jpeg/exif-samples/11-tests.jpg"

	in, err := os.Open(src)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer func() { _ = in.Close() }()

	tmp, err := os.CreateTemp("", "gometadata-exposure-*.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer func() {
		_ = tmp.Close()
		_ = os.Remove(tmp.Name())
	}()

	if _, err := io.Copy(tmp, in); err != nil {
		fmt.Println("error:", err)
		return
	}
	_ = tmp.Close()

	m, err := gometadata.ReadFile(tmp.Name())
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	m.SetExposureTime(1, 125)
	m.SetFNumber(4.0)
	m.SetISO(800)
	m.SetFocalLength(35.0)

	if err := gometadata.WriteFile(tmp.Name(), m); err != nil {
		fmt.Println("error:", err)
		return
	}

	m2, err := gometadata.ReadFile(tmp.Name())
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	if num, den, ok := m2.ExposureTime(); ok {
		fmt.Printf("exposure time: %d/%d s\n", num, den)
	}
	if fn, ok := m2.FNumber(); ok {
		fmt.Printf("f-number:      f/%.1f\n", fn)
	}
	if iso, ok := m2.ISO(); ok {
		fmt.Printf("ISO:           %d\n", iso)
	}
	if fl, ok := m2.FocalLength(); ok {
		fmt.Printf("focal length:  %.1f mm\n", fl)
	}
}
Output:
exposure time: 1/125 s
f-number:      f/4.0
ISO:           800
focal length:  35.0 mm
Example (Orientation)

ExampleWriteFile_orientation demonstrates writing EXIF tag 0x0112 to record how a display application should rotate the image for correct presentation. Value 6 means 90° clockwise — the standard encoding for a portrait photo shot in landscape mode. Other common values: 1 = normal, 3 = 180°, 8 = 90° counter-clockwise (EXIF §4.6.4, CIPA DC-008-2019).

package main

import (
	"fmt"
	"io"
	"os"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	const src = "testdata/corpus/jpeg/exif-samples/11-tests.jpg"

	in, err := os.Open(src)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer func() { _ = in.Close() }()

	tmp, err := os.CreateTemp("", "gometadata-orient-*.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer func() {
		_ = tmp.Close()
		_ = os.Remove(tmp.Name())
	}()

	if _, err := io.Copy(tmp, in); err != nil {
		fmt.Println("error:", err)
		return
	}
	_ = tmp.Close()

	m, err := gometadata.ReadFile(tmp.Name())
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	m.SetOrientation(6) // 90° clockwise — portrait-mode capture on a landscape sensor
	if err := gometadata.WriteFile(tmp.Name(), m); err != nil {
		fmt.Println("error:", err)
		return
	}

	m2, err := gometadata.ReadFile(tmp.Name())
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	o, ok := m2.Orientation()
	if !ok {
		fmt.Println("no orientation")
		return
	}
	fmt.Printf("orientation: %d\n", o)
}
Output:
orientation: 6

Types

type CorruptMetadataError

type CorruptMetadataError = metaerr.CorruptMetadataError

CorruptMetadataError is returned when a metadata segment is structurally invalid (bad offsets, impossible lengths, invalid tag types, etc.). Alias of internal/metaerr.CorruptMetadataError; all sub-packages use the same type.

type Metadata

type Metadata struct {
	EXIF *exif.EXIF
	IPTC *iptc.IPTC
	XMP  *xmp.XMP
	// contains filtered or unexported fields
}

Metadata holds all metadata extracted from an image. Any of the three embedded pointers may be nil if that metadata type was not present in the image.

When the same field exists in more than one metadata type, the convenience methods below apply a documented resolution policy:

  • Camera data (model, make, lens, settings): EXIF wins.
  • Descriptive and rights data (caption, copyright, keywords): XMP wins, falling back to IPTC, then EXIF.
Example (SubjectPriority)

ExampleMetadata_subjectPriority demonstrates the source-priority policy applied by GoMetadata when the same field appears in multiple metadata layers. For rights and descriptive fields — Copyright, Caption, and Creator — XMP is preferred over IPTC over EXIF. This eliminates ambiguity without requiring the caller to check each layer individually. The IPTC Photo Metadata reference file carries matching copyright in both XMP and IPTC; GoMetadata returns the XMP value and the caller receives one unambiguous answer.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/iptc/IPTC-PhotometadataRef-Std2021.1.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// All three layers are present in this reference image.
	fmt.Println("XMP present: ", m.XMP != nil)
	fmt.Println("IPTC present:", m.IPTC != nil)

	// Copyright follows XMP > IPTC > EXIF priority; the XMP value wins here.
	fmt.Println("copyright source: XMP")
	fmt.Println("copyright:", m.Copyright())
}
Output:
XMP present:  true
IPTC present: true
copyright source: XMP
copyright: Copyright (Notice) 2021.1 IPTC - www.iptc.org  (ref2021.1)

func NewMetadata

func NewMetadata(fmtID format.FormatID) *Metadata

NewMetadata returns an empty Metadata ready for writing to a file of the given format. All metadata fields are nil; populate m.EXIF, m.IPTC, or m.XMP before passing m to Write or WriteFile.

Example

ExampleNewMetadata demonstrates creating a Metadata value from scratch — useful when embedding metadata into a freshly generated image rather than reading an existing one.

NewMetadata returns a Metadata with nil EXIF, IPTC, and XMP fields. To populate them, assign fully-constructed structs from the sub-packages before calling Write or WriteFile.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
	"github.com/FlavioCFOliveira/GoMetadata/format"
)

func main() {
	// NewMetadata records the target container format so that the Write path
	// knows which segment layout to produce.
	m := gometadata.NewMetadata(format.FormatJPEG)

	// All three sub-struct fields are nil until you assign them.
	fmt.Println("format:", m.Format())
	fmt.Println("EXIF nil:", m.EXIF == nil)
	fmt.Println("IPTC nil:", m.IPTC == nil)
	fmt.Println("XMP nil:", m.XMP == nil)
}
Output:
format: JPEG
EXIF nil: true
IPTC nil: true
XMP nil: true

func Read

func Read(r io.ReadSeeker, opts ...ReadOption) (*Metadata, error)

Read reads all metadata from r. The format is detected automatically from magic bytes; r must support seeking (io.ReadSeeker). A nil error means at least one metadata type was successfully parsed; individual fields may still be nil.

Example

ExampleRead demonstrates reading metadata from an io.ReadSeeker instead of a file path. This is useful when the image data is already in memory or when reading from a network stream.

package main

import (
	"fmt"
	"os"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	f, err := os.Open("testdata/corpus/jpeg/exif-samples/11-tests.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer func() { _ = f.Close() }()

	// Read accepts any io.ReadSeeker — os.File, bytes.Reader, etc.
	m, err := gometadata.Read(f)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println("Format:", m.Format())
	fmt.Println("Model: ", m.CameraModel())
}
Output:
Format: JPEG
Model:  Canon DIGITAL IXUS 40

func ReadFile

func ReadFile(path string, opts ...ReadOption) (*Metadata, error)

ReadFile opens the file at path and reads all metadata from it. It is a convenience wrapper around Read.

Example

ExampleReadFile demonstrates reading camera metadata from a JPEG file. The library detects the format automatically from magic bytes and parses all three metadata layers (EXIF, IPTC, XMP) in a single call.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/11-tests.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println("Format:  ", m.Format())
	fmt.Println("Make:    ", m.Make())
	fmt.Println("Model:   ", m.CameraModel())
	if lens := m.LensModel(); lens != "" {
		fmt.Println("Lens:    ", lens)
	} else {
		fmt.Println("Lens:")
	}
	if sw := m.Software(); sw != "" {
		fmt.Println("Software:", sw)
	} else {
		fmt.Println("Software:")
	}
}
Output:
Format:   JPEG
Make:     Canon
Model:    Canon DIGITAL IXUS 40
Lens:
Software:
Example (ExposureData)

ExampleReadFile_exposureData demonstrates reading shooting parameters. ExposureTime is returned as a rational (numerator/denominator) so that fractional values like 1/250 s can be represented exactly.

package main

import (
	"fmt"
	"time"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/11-tests.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	if num, den, ok := m.ExposureTime(); ok {
		if num == 1 {
			fmt.Printf("ExposureTime: 1/%d s\n", den)
		} else {
			fmt.Printf("ExposureTime: %d/%d s\n", num, den)
		}
	}

	if fn, ok := m.FNumber(); ok {
		fmt.Printf("FNumber:      f/%.1f\n", fn)
	}

	if iso, ok := m.ISO(); ok {
		fmt.Printf("ISO:          %d\n", iso)
	}

	if fl, ok := m.FocalLength(); ok {
		fmt.Printf("FocalLength:  %.1f mm\n", fl)
	}

	if t, ok := m.DateTimeOriginal(); ok {
		fmt.Printf("Captured:     %s\n", t.Format(time.RFC3339))
	}
}
Output:
ExposureTime: 1/500 s
FNumber:      f/2.8
FocalLength:  5.8 mm
Captured:     2007-09-03T16:03:45Z
Example (Gps)

ExampleReadFile_gps demonstrates extracting GPS coordinates. The ok return value is false when no GPS data is present in the image.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/11-tests.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	lat, lon, ok := m.GPS()
	if !ok {
		fmt.Println("no GPS data")
		return
	}
	fmt.Printf("lat=%.6f lon=%.6f\n", lat, lon)
}
Output:
no GPS data
Example (IptcOnly)

ExampleReadFile_iptcOnly demonstrates combining WithoutEXIF and WithoutXMP to produce an IPTC-only parse — the fastest path for news agency tools that need caption (IPTC dataset 2:120) and copyright (dataset 2:116) without the overhead of EXIF IFD traversal or RDF/XML parsing. Only the APP13/Photoshop IRB segment is decoded.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile(
		"testdata/corpus/jpeg/iptc/IPTC-PhotometadataRef-Std2021.1.jpg",
		gometadata.WithoutEXIF(),
		gometadata.WithoutXMP(),
	)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println("EXIF skipped:", m.EXIF == nil)
	fmt.Println("XMP skipped: ", m.XMP == nil)
	fmt.Println("IPTC present:", m.IPTC != nil)
	fmt.Println("caption:     ", m.Caption())
}
Output:
EXIF skipped: true
XMP skipped:  true
IPTC present: true
caption:      The description aka caption (ref2021.1)
Example (Options)

ExampleReadFile_options demonstrates selective parsing for performance. Skipping XMP and MakerNote reduces parse latency when only EXIF and IPTC data are needed — useful in batch-processing pipelines.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	// WithoutXMP skips RDF/XML parsing; WithoutMakerNote skips the
	// manufacturer-specific IFD (often the largest part of an EXIF block).
	// EXIF and IPTC are still fully parsed.
	m, err := gometadata.ReadFile(
		"testdata/corpus/jpeg/exif-samples/11-tests.jpg",
		gometadata.WithoutXMP(),
		gometadata.WithoutMakerNote(),
	)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// XMP is nil because we skipped it; EXIF and IPTC are available.
	fmt.Println("XMP skipped:", m.XMP == nil)
	fmt.Println("Model:      ", m.CameraModel())
}
Output:
XMP skipped: true
Model:       Canon DIGITAL IXUS 40
Example (WithoutEXIF)

ExampleReadFile_withoutEXIF demonstrates the WithoutEXIF option, which skips all EXIF IFD parsing. Use this in news DAM systems or caption-indexing pipelines that only need IPTC caption and copyright data — EXIF parsing is typically the most expensive step, especially for large RAW files. IPTC and XMP layers are still fully parsed.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile(
		"testdata/corpus/jpeg/iptc/IPTC-PhotometadataRef-Std2021.1.jpg",
		gometadata.WithoutEXIF(),
	)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println("EXIF skipped:", m.EXIF == nil)
	fmt.Println("IPTC present:", m.IPTC != nil)
	fmt.Printf("keywords: %d\n", len(m.Keywords()))
}
Output:
EXIF skipped: true
IPTC present: true
keywords: 3
Example (WithoutIPTC)

ExampleReadFile_withoutIPTC demonstrates the WithoutIPTC option, which skips APP13/Photoshop IRB parsing. Use this when only EXIF camera data and GPS are needed — for example in a geotagging or camera-statistics pipeline — saving the IPTC parsing overhead. EXIF and XMP layers are still fully parsed.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile(
		"testdata/corpus/jpeg/exif-samples/Canon_40D.jpg",
		gometadata.WithoutIPTC(),
	)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println("IPTC skipped:", m.IPTC == nil)
	fmt.Println("EXIF present:", m.EXIF != nil)
	fmt.Println("model:       ", m.CameraModel())
}
Output:
IPTC skipped: true
EXIF present: true
model:        Canon EOS 40D

func (*Metadata) Altitude

func (m *Metadata) Altitude() (float64, bool)

Altitude returns the GPS altitude in metres above (positive) or below (negative) sea level from the GPS IFD (EXIF §4.6.6 tags 0x0005/0x0006). ok is false when not present.

Example

ExampleMetadata_Altitude demonstrates reading GPS altitude from the GPS IFD (EXIF §4.6.6, tags 0x0005 and 0x0006). The altitude is returned in metres above sea level; a negative value means below sea level. The Samsung SM-G930F stores GPS coordinates and altitude when Location Services are enabled.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/67-0_length_string.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	alt, ok := m.Altitude()
	if !ok {
		fmt.Println("no altitude data")
		return
	}
	fmt.Printf("altitude: %.2f m\n", alt)
}
Output:
altitude: 340.00 m

func (*Metadata) CameraModel

func (m *Metadata) CameraModel() string

CameraModel returns the camera model string. Source priority: EXIF > XMP.

func (*Metadata) Caption

func (m *Metadata) Caption() string

Caption returns the image description / caption. Source priority: XMP > IPTC > EXIF.

func (*Metadata) ColorSpace

func (m *Metadata) ColorSpace() (uint16, bool)

ColorSpace returns the colour space from ExifIFD tag 0xA001. 1 = sRGB, 0xFFFF = uncalibrated (EXIF §4.6.5). ok is false when not present.

Example

ExampleMetadata_ColorSpace demonstrates reading the EXIF ColorSpace tag (0xA001). The value 1 means sRGB — the standard colour space for consumer cameras and web images. The value 65535 (0xFFFF) means uncalibrated or a non-sRGB colour space such as Adobe RGB (EXIF §4.6.5).

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/11-tests.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	cs, ok := m.ColorSpace()
	if !ok {
		fmt.Println("no color space data")
		return
	}

	var label string
	switch cs {
	case 1:
		label = "sRGB"
	case 65535:
		label = "uncalibrated"
	default:
		label = "unknown"
	}
	fmt.Printf("color space: %d (%s)\n", cs, label)
}
Output:
color space: 1 (sRGB)

func (*Metadata) Copyright

func (m *Metadata) Copyright() string

Copyright returns the copyright notice. Source priority: XMP > IPTC > EXIF.

func (*Metadata) Creator

func (m *Metadata) Creator() string

Creator returns the author / creator name. Source priority: XMP > IPTC > EXIF.

func (*Metadata) DateTime

func (m *Metadata) DateTime() (time.Time, bool)

DateTime returns the general date/time the image was last changed (IFD0 DateTime). Source: EXIF only (tag 0x0132). ok is false when not present.

func (*Metadata) DateTimeOriginal

func (m *Metadata) DateTimeOriginal() (time.Time, bool)

DateTimeOriginal returns the original capture date/time. Source priority: EXIF > XMP.

Example

ExampleMetadata_DateTimeOriginal demonstrates parsing and formatting the original capture date/time of an image.

package main

import (
	"fmt"
	"time"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/11-tests.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	t, ok := m.DateTimeOriginal()
	if !ok {
		fmt.Println("no capture time")
		return
	}
	// Format with the standard Go time package — no special knowledge of the
	// EXIF "YYYY:MM:DD HH:MM:SS" format is required.
	fmt.Println("captured:", t.Format(time.RFC3339))
}
Output:
captured: 2007-09-03T16:03:45Z

func (*Metadata) DigitalZoomRatio

func (m *Metadata) DigitalZoomRatio() (float64, bool)

DigitalZoomRatio returns the digital zoom ratio from ExifIFD tag 0xA404. 0 = not used. ok is false when not present.

func (*Metadata) ExposureMode

func (m *Metadata) ExposureMode() (uint16, bool)

ExposureMode returns the exposure mode from ExifIFD tag 0xA402. 0 = auto, 1 = manual, 2 = auto bracket (EXIF §4.6.5). ok is false when not present.

func (*Metadata) ExposureTime

func (m *Metadata) ExposureTime() (num, den uint32, ok bool)

ExposureTime returns the exposure time as [numerator, denominator] in seconds. Source: EXIF only (no equivalent in XMP/IPTC at this level).

func (*Metadata) FNumber

func (m *Metadata) FNumber() (float64, bool)

FNumber returns the F-number (aperture). Source: EXIF only.

func (*Metadata) Flash

func (m *Metadata) Flash() (uint16, bool)

Flash returns the flash status from ExifIFD tag 0x9209. Bit 0 = flash fired; see EXIF §4.6.5 for full bitmask meaning. ok is false when not present.

Example

ExampleMetadata_Flash demonstrates reading the raw EXIF Flash tag (0x9209). Bit 0 of the returned value indicates whether the flash fired. Bits 1–2 encode strobe return status; bits 3–4 encode flash mode; bit 5 indicates flash presence; bit 6 indicates red-eye reduction (EXIF §4.6.5).

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/Canon_40D.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	flash, ok := m.Flash()
	if !ok {
		fmt.Println("no flash data")
		return
	}

	fired := flash&0x01 != 0
	fmt.Printf("flash value: %d\n", flash)
	fmt.Printf("flash fired: %v\n", fired)
}
Output:
flash value: 9
flash fired: true

func (*Metadata) FocalLength

func (m *Metadata) FocalLength() (float64, bool)

FocalLength returns the focal length in millimetres. Source: EXIF only.

func (*Metadata) Format

func (m *Metadata) Format() format.FormatID

Format returns the detected container format ID of the image.

func (*Metadata) GPS

func (m *Metadata) GPS() (float64, float64, bool)

GPS returns the GPS coordinates in decimal degrees (WGS-84). ok is false when no GPS data is present. Source priority: EXIF > XMP.

func (*Metadata) ISO

func (m *Metadata) ISO() (uint, bool)

ISO returns the ISO speed rating. Source: EXIF only.

func (*Metadata) ImageSize

func (m *Metadata) ImageSize() (width, height uint32, ok bool)

ImageSize returns the pixel dimensions of the full-resolution image. Source: EXIF only (PixelXDimension / PixelYDimension, EXIF §4.6.5).

Example

ExampleMetadata_ImageSize demonstrates reading the pixel dimensions stored in the EXIF PixelXDimension (0xA002) and PixelYDimension (0xA003) tags. These record the dimensions of the compressed image data rather than the full sensor capture, making them the authoritative size for display and storage calculations in GoMetadata.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/11-tests.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	width, height, ok := m.ImageSize()
	if !ok {
		fmt.Println("no image size")
		return
	}
	fmt.Printf("width:  %d px\n", width)
	fmt.Printf("height: %d px\n", height)
}
Output:
width:  2272 px
height: 1704 px

func (*Metadata) Keywords

func (m *Metadata) Keywords() []string

Keywords returns the subject keywords. Source priority: XMP > IPTC.

Example

ExampleMetadata_Keywords demonstrates reading keywords and adding to them.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/iptc/IPTC-PhotometadataRef-Std2021.1.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	existing := m.Keywords()
	fmt.Printf("existing keywords: %d\n", len(existing))

	// Append a new keyword to the existing set and update the Metadata.
	m.SetKeywords(append(existing, "gometadata-example"))
	fmt.Printf("updated keywords: %d\n", len(m.Keywords()))
}
Output:
existing keywords: 3
updated keywords: 4

func (*Metadata) LensModel

func (m *Metadata) LensModel() string

LensModel returns the lens model string. Source priority: EXIF > XMP.

func (*Metadata) Make

func (m *Metadata) Make() string

Make returns the camera manufacturer string. Source priority: EXIF > XMP (tiff:Make).

Example

ExampleMetadata_Make demonstrates reading the camera manufacturer alongside the camera model from the same EXIF IFD0 block (tags 0x010F and 0x0110 respectively). Both fields also fall back to XMP tiff:Make and tiff:Model when EXIF is absent. They follow the EXIF-first, XMP-fallback priority policy used throughout GoMetadata for camera-identity fields.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/11-tests.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println("make: ", m.Make())
	fmt.Println("model:", m.CameraModel())
}
Output:
make:  Canon
model: Canon DIGITAL IXUS 40

func (*Metadata) MeteringMode

func (m *Metadata) MeteringMode() (uint16, bool)

MeteringMode returns the metering mode from ExifIFD tag 0x9207 (EXIF §4.6.5). ok is false when not present.

func (*Metadata) Orientation

func (m *Metadata) Orientation() (uint16, bool)

Orientation returns the image orientation (1–8 per EXIF §4.6.4). Source: EXIF only.

Example

ExampleMetadata_Orientation demonstrates reading EXIF tag 0x0112, which describes how the image should be rotated or flipped for correct display. Value 1 means no rotation needed; value 6 means 90° clockwise — the standard encoding for a portrait photo captured in landscape mode on a Canon camera.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/canon_hdr_YES.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	o, ok := m.Orientation()
	if !ok {
		fmt.Println("no orientation")
		return
	}

	// Map common EXIF orientation values to human-readable descriptions.
	descriptions := map[uint16]string{
		1: "normal (no rotation)",
		3: "180° rotation",
		6: "90° clockwise",
		8: "90° counter-clockwise",
	}
	desc, known := descriptions[o]
	if !known {
		desc = "see EXIF §4.6.4 for full table"
	}
	fmt.Printf("orientation: %d (%s)\n", o, desc)
}
Output:
orientation: 6 (90° clockwise)

func (*Metadata) RawEXIF

func (m *Metadata) RawEXIF() []byte

RawEXIF returns the raw EXIF segment bytes as read from the container.

func (*Metadata) RawIPTC

func (m *Metadata) RawIPTC() []byte

RawIPTC returns the raw IPTC IIM segment bytes as read from the container.

func (*Metadata) RawXMP

func (m *Metadata) RawXMP() []byte

RawXMP returns the raw XMP packet bytes as read from the container.

func (*Metadata) SceneType

func (m *Metadata) SceneType() (byte, bool)

SceneType returns the scene capture type byte from ExifIFD tag 0xA301. 0 = directly photographed (EXIF §4.6.5). ok is false when not present.

func (*Metadata) SetCameraModel

func (m *Metadata) SetCameraModel(s string)

SetCameraModel writes s to EXIF and XMP when those components are non-nil.

func (*Metadata) SetCaption

func (m *Metadata) SetCaption(s string)

SetCaption writes s to all non-nil metadata components (EXIF, IPTC, XMP).

func (*Metadata) SetCopyright

func (m *Metadata) SetCopyright(s string)

SetCopyright writes s to all non-nil metadata components (EXIF, IPTC, XMP).

func (*Metadata) SetCreator

func (m *Metadata) SetCreator(s string)

SetCreator writes s to all non-nil metadata components (EXIF, IPTC, XMP).

func (*Metadata) SetDateTimeOriginal

func (m *Metadata) SetDateTimeOriginal(t time.Time)

SetDateTimeOriginal writes t to EXIF and XMP when those components are non-nil.

func (*Metadata) SetExposureTime

func (m *Metadata) SetExposureTime(num, den uint32)

SetExposureTime writes the rational exposure time to EXIF when non-nil.

func (*Metadata) SetFNumber

func (m *Metadata) SetFNumber(f float64)

SetFNumber writes the F-number to EXIF when non-nil.

func (*Metadata) SetFocalLength

func (m *Metadata) SetFocalLength(mm float64)

SetFocalLength writes the focal length in millimetres to EXIF when non-nil.

func (*Metadata) SetGPS

func (m *Metadata) SetGPS(lat, lon float64)

SetGPS writes the WGS-84 decimal-degree coordinates to EXIF and XMP when those components are non-nil.

func (*Metadata) SetISO

func (m *Metadata) SetISO(iso uint)

SetISO writes the ISO speed rating to EXIF when non-nil.

func (*Metadata) SetImageSize

func (m *Metadata) SetImageSize(width, height uint32)

SetImageSize writes the pixel dimensions to EXIF when non-nil.

func (*Metadata) SetKeywords

func (m *Metadata) SetKeywords(kws []string)

SetKeywords writes kws to IPTC and XMP when those components are non-nil.

func (*Metadata) SetLensModel

func (m *Metadata) SetLensModel(s string)

SetLensModel writes s to EXIF and XMP when those components are non-nil.

func (*Metadata) SetMake

func (m *Metadata) SetMake(s string)

SetMake writes s to EXIF when it is non-nil.

func (*Metadata) SetOrientation

func (m *Metadata) SetOrientation(v uint16)

SetOrientation writes the orientation tag to EXIF when non-nil.

func (*Metadata) Software

func (m *Metadata) Software() string

Software returns the software / firmware string used to produce the image. Source priority: EXIF > XMP (xmp:CreatorTool).

func (*Metadata) SubjectDistance

func (m *Metadata) SubjectDistance() (float64, bool)

SubjectDistance returns the distance to the subject in metres from ExifIFD tag 0x9206. ok is false when not present or denominator is zero.

func (*Metadata) Validate

func (m *Metadata) Validate() error

Validate checks that m is in a consistent state suitable for writing. It returns a descriptive error when an obvious inconsistency is detected (e.g. unknown format, EXIF struct without IFD0). Write calls Validate automatically; callers may call it earlier for better error messages.

func (*Metadata) WhiteBalance

func (m *Metadata) WhiteBalance() (uint16, bool)

WhiteBalance returns the white balance mode from ExifIFD tag 0xA403. 0 = auto, 1 = manual (EXIF §4.6.5). ok is false when not present.

Example

ExampleMetadata_WhiteBalance demonstrates reading the EXIF WhiteBalance tag (0xA403). A value of 0 means the camera chose the balance automatically; 1 means manual white balance was set by the photographer (EXIF §4.6.5). Knowing whether white balance was manual helps RAW processing pipelines decide whether to trust or recalculate the recorded colour temperature.

package main

import (
	"fmt"

	gometadata "github.com/FlavioCFOliveira/GoMetadata"
)

func main() {
	m, err := gometadata.ReadFile("testdata/corpus/jpeg/exif-samples/jolla.jpg")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	wb, ok := m.WhiteBalance()
	if !ok {
		fmt.Println("no white balance data")
		return
	}

	mode := "auto"
	if wb == 1 {
		mode = "manual"
	}
	fmt.Printf("white balance: %d (%s)\n", wb, mode)
}
Output:
white balance: 1 (manual)

type ReadOption

type ReadOption func(*readConfig)

ReadOption configures a Read or ReadFile call.

func WithoutEXIF

func WithoutEXIF() ReadOption

WithoutEXIF skips EXIF parsing.

func WithoutIPTC

func WithoutIPTC() ReadOption

WithoutIPTC skips IPTC parsing.

func WithoutMakerNote

func WithoutMakerNote() ReadOption

WithoutMakerNote skips manufacturer-specific MakerNote IFD parsing. The raw MakerNote bytes are still retained for round-trip writes; only the decoded EXIF.MakerNoteIFD field is omitted. Use this when extension tags are not needed and you want to minimise parse latency on camera RAW files.

func WithoutXMP

func WithoutXMP() ReadOption

WithoutXMP skips XMP parsing, reducing allocations when XMP is not needed.

type TruncatedFileError

type TruncatedFileError = metaerr.TruncatedFileError

TruncatedFileError is returned when the input ends unexpectedly before a required structure could be read. Alias of internal/metaerr.TruncatedFileError; all sub-packages use the same type.

type UnsupportedFormatError

type UnsupportedFormatError struct {
	// Magic contains the first bytes read from the input.
	Magic [12]byte
}

UnsupportedFormatError is returned when the magic bytes of the input do not match any supported image container format.

func (*UnsupportedFormatError) Error

func (e *UnsupportedFormatError) Error() string

type WriteOption

type WriteOption func(*writeConfig)

WriteOption configures a Write or WriteFile call.

func PreserveUnknownSegments

func PreserveUnknownSegments(v bool) WriteOption

PreserveUnknownSegments keeps APP or chunk segments that this library does not recognise, passing them through unchanged. Default: true.

Directories

Path Synopsis
examples
batch-keywords command
Command batch-keywords appends a keyword to every image file in a directory tree.
Command batch-keywords appends a keyword to every image file in a directory tree.
copyright-stamp command
Command copyright-stamp applies a standardised copyright notice, creator name, caption, and keyword set to every image file in a directory tree.
Command copyright-stamp applies a standardised copyright notice, creator name, caption, and keyword set to every image file in a directory tree.
gallery-sidecar command
Command gallery-sidecar reads metadata from one or more image files and prints a JSON representation suitable for use as a sidecar file, search index entry, or API response payload.
Command gallery-sidecar reads metadata from one or more image files and prints a JSON representation suitable for use as a sidecar file, search index entry, or API response payload.
multi-format-roundtrip command
Command multi-format-roundtrip demonstrates reading and writing image metadata across all container formats supported by GoMetadata: JPEG, TIFF, PNG, WebP, HEIF/HEIC, and camera RAW variants (Canon CR2/CR3, Nikon NEF, Sony ARW, Adobe DNG, Olympus ORF, Panasonic RW2).
Command multi-format-roundtrip demonstrates reading and writing image metadata across all container formats supported by GoMetadata: JPEG, TIFF, PNG, WebP, HEIF/HEIC, and camera RAW variants (Canon CR2/CR3, Nikon NEF, Sony ARW, Adobe DNG, Olympus ORF, Panasonic RW2).
raw-inspector command
Command raw-inspector reads and displays all available metadata from camera RAW files.
Command raw-inspector reads and displays all available metadata from camera RAW files.
read-metadata command
Command read-metadata prints all available metadata from an image file.
Command read-metadata prints all available metadata from an image file.
stream-transcode command
Command stream-transcode reads an image from stdin, applies metadata supplied via flags, and writes the modified image to stdout.
Command stream-transcode reads an image from stdin, applies metadata supplied via flags, and writes the modified image to stdout.
write-metadata command
Command write-metadata applies metadata fields to an image file and writes the result to an output path.
Command write-metadata applies metadata fields to an image file and writes the result to an output path.
Package exif implements an EXIF/TIFF parser and writer.
Package exif implements an EXIF/TIFF parser and writer.
makernote
Package makernote dispatches MakerNote parsing to the appropriate manufacturer-specific sub-package based on the IFD0 Make tag value.
Package makernote dispatches MakerNote parsing to the appropriate manufacturer-specific sub-package based on the IFD0 Make tag value.
makernote/canon
Package canon parses Canon MakerNote IFDs.
Package canon parses Canon MakerNote IFDs.
makernote/dji
Package dji parses DJI drone MakerNote IFDs.
Package dji parses DJI drone MakerNote IFDs.
makernote/fujifilm
Package fujifilm parses Fujifilm MakerNote IFDs.
Package fujifilm parses Fujifilm MakerNote IFDs.
makernote/leica
Package leica parses Leica MakerNote IFDs.
Package leica parses Leica MakerNote IFDs.
makernote/nikon
Package nikon parses Nikon MakerNote IFDs.
Package nikon parses Nikon MakerNote IFDs.
makernote/olympus
Package olympus parses Olympus MakerNote IFDs.
Package olympus parses Olympus MakerNote IFDs.
makernote/panasonic
Package panasonic parses Panasonic/Lumix MakerNote IFDs.
Package panasonic parses Panasonic/Lumix MakerNote IFDs.
makernote/pentax
Package pentax parses Pentax MakerNote IFDs.
Package pentax parses Pentax MakerNote IFDs.
makernote/samsung
Package samsung parses Samsung MakerNote IFDs.
Package samsung parses Samsung MakerNote IFDs.
makernote/sigma
Package sigma parses Sigma MakerNote IFDs.
Package sigma parses Sigma MakerNote IFDs.
makernote/sony
Package sony parses Sony MakerNote IFDs.
Package sony parses Sony MakerNote IFDs.
Package format provides image container detection and metadata extraction/injection for all supported formats.
Package format provides image container detection and metadata extraction/injection for all supported formats.
heif
Package heif implements extraction and injection of EXIF and XMP metadata within HEIF/HEIC files (ISO 23008-12 / ISO 14496-12 ISOBMFF).
Package heif implements extraction and injection of EXIF and XMP metadata within HEIF/HEIC files (ISO 23008-12 / ISO 14496-12 ISOBMFF).
jpeg
Package jpeg implements extraction and injection of EXIF, IPTC, and XMP metadata segments within JPEG files.
Package jpeg implements extraction and injection of EXIF, IPTC, and XMP metadata segments within JPEG files.
png
Package png implements extraction and injection of EXIF and XMP metadata within PNG files.
Package png implements extraction and injection of EXIF and XMP metadata within PNG files.
raw/arw
Package arw implements metadata extraction for Sony ARW files.
Package arw implements metadata extraction for Sony ARW files.
raw/cr2
Package cr2 implements metadata extraction for Canon CR2 files.
Package cr2 implements metadata extraction for Canon CR2 files.
raw/cr3
Package cr3 implements metadata extraction for Canon CR3 files.
Package cr3 implements metadata extraction for Canon CR3 files.
raw/dng
Package dng implements metadata extraction for Adobe DNG files.
Package dng implements metadata extraction for Adobe DNG files.
raw/nef
Package nef implements metadata extraction for Nikon NEF files.
Package nef implements metadata extraction for Nikon NEF files.
raw/orf
Package orf implements metadata extraction for Olympus ORF files.
Package orf implements metadata extraction for Olympus ORF files.
raw/rw2
Package rw2 implements metadata extraction for Panasonic RW2 files.
Package rw2 implements metadata extraction for Panasonic RW2 files.
tiff
Package tiff implements extraction and injection of metadata within TIFF container files.
Package tiff implements extraction and injection of metadata within TIFF container files.
webp
Package webp implements extraction and injection of EXIF and XMP metadata within WebP files.
Package webp implements extraction and injection of EXIF and XMP metadata within WebP files.
internal
bmff
Package bmff provides a minimal ISO Base Media File Format (ISO 14496-12) box reader used by the heif and cr3 container packages.
Package bmff provides a minimal ISO Base Media File Format (ISO 14496-12) box reader used by the heif and cr3 container packages.
byteorder
Package byteorder provides zero-allocation big-endian and little-endian integer reads from byte slices.
Package byteorder provides zero-allocation big-endian and little-endian integer reads from byte slices.
iobuf
Package iobuf provides sync.Pool-backed reusable byte buffers for use in performance-critical parsing paths.
Package iobuf provides sync.Pool-backed reusable byte buffers for use in performance-critical parsing paths.
metaerr
Package metaerr defines shared error types for metadata parsing and writing.
Package metaerr defines shared error types for metadata parsing and writing.
riff
Package riff provides a minimal RIFF chunk reader used by the webp container package.
Package riff provides a minimal RIFF chunk reader used by the webp container package.
testutil
Package testutil provides shared helpers for tests across all packages.
Package testutil provides shared helpers for tests across all packages.
Package iptc implements an IPTC IIM parser and writer.
Package iptc implements an IPTC IIM parser and writer.
Package xmp implements an XMP packet parser and writer.
Package xmp implements an XMP packet parser and writer.

Jump to

Keyboard shortcuts

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