exiftool

package module
v0.0.0-...-fd0c127 Latest Latest
Warning

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

Go to latest
Published: May 10, 2026 License: MIT Imports: 22 Imported by: 0

README

go-exiftool: ExifTool in Go (zeroperl + wasm2go)

Go Reference Go Version Go Report Card Release CI codecov

Go library that runs ExifTool in-process: embedded Perl via zeroperl (Perl compiled to WebAssembly), executed as native Go via a wasm2go-generated module. You do not need a system exiftool binary or a separate Perl install.

Upstream ExifTool (Phil Harvey) and metadata: exiftool.org, exiftool/exiftool.

Why this shape

  • Self-contained — Perl stdlib, ExifTool, and the interpreter ship inside the module (embed/). The stdlib is stored LZ4-compressed; perlFS() transparently decompresses files on first read using a 2000-entry LRU cache (lbe/cfsread).
  • Same behavior everywhere — one toolchain-based interpreter implementation on Linux, macOS, Windows, etc.
  • Trade-off — cold start is heavy (on the order of ~7–10 seconds to initialize the wasm2go-backed Perl runtime). For many operations, use NewServer and Server.Command so ExifTool stays open (-stay_open) and work is amortized.

Requirements

  • Go 1.26+
  • Dependencies are listed in go.mod.

Usage

// One-shot: paths are relative to the process working directory (host cwd is
// layered read-only under "/" alongside the embedded perl prefix).
out, err := exiftool.Command(nil, "-json", "photo.jpg")

// Batch: one Perl startup, many commands.
e, err := exiftool.NewServer("-fast")
if err != nil { /* ... */ }
defer e.Shutdown()
out, err = e.Command("-Artist", "photo.jpg")

Package docs, API details, and filesystem semantics (mount layout, temp dir, Arg1 / Config): run go doc -all or open pkg.go.dev.

Development

Tests
go test ./... -timeout 10m   # allow several minutes for interpreter-heavy paths
Refreshing embed/ from zeroperl

The generated zeroperl Go source and Perl install prefix come from zeroperl. Follow that repository’s Build section (Docker or Apple Container): build the image, run the container, and copy /artifacts into a host directory (as shown there, e.g. ./output/).

From the build output directory, install these into this repo under embed/:

Artifact from zeroperl output Path in go-exiftool
zeroperl.go internal/zeroperl/zeroperl.go
perl-wasi-prefix/ (entire tree) embed/perl-wasi-prefix/

Use the generated zeroperl.go artifact unless you intentionally switch runtimes. The ExifTool script is loaded from embed/perl-wasi-prefix/bin/exiftool in the embedded prefix.

After copying perl-wasi-prefix/ into embed/, run go generate ./... to re-compress the tree with cfsread-lz4. This updates the LZ4-compressed files in place so the embedded binary reflects the new Perl build.

zeroperl’s README also documents build arguments (PERL_VERSION, EXIFTOOL_VERSION, BUILD_EXIFTOOL, memory/stack, etc.). If you change PERL_VERSION, update the perl5Lib segment in perlversion.go so the runtime PERL5LIB path (/lib/<segment>/…) stays aligned with the embedded prefix layout.

Building ExifTool prefix with dist.pl

Use dist.pl to download, build, minify, test, and install ExifTool into embed/perl-wasi-prefix/.

perl dist.pl --exiftool-version 13.56

Version resolution precedence is:

  1. --exiftool-version
  2. EXIFTOOL_VERSION environment variable
  3. Latest version parsed from https://exiftool.org/history.html

License

See LICENSE. ExifTool and embedded components carry their own licenses under embed/.

Documentation

Overview

cachefs.go provides cachedFS, an internal fs.FS implementation that wraps any underlying fs.FS with transparent LZ4 decompression and LRU caching via github.com/lbe/cfsread.

cachedFS is used by perlFS() to serve the embedded perl-wasi-prefix tree: the embedded .pm files are stored LZ4-compressed in the binary, and cachedFS decompresses them on first access and caches the result for subsequent reads. Concurrent accesses to the same path are coalesced via singleflight so only one goroutine performs I/O and decompression.

Package exiftool runs ExifTool in-process via an embedded Perl interpreter (zeroperl) compiled to native Go via wasm2go with a custom WASI host layer. No external exiftool binary or Perl installation is required.

The Perl stdlib and ExifTool modules are embedded as LZ4-compressed files and transparently decompressed on first read via an LRU cache.

Entry points:

Configuration package variables Arg1 and Config are forwarded into the ExifTool argument list for Command/CommandContext and NewServer. Exec is kept for API compatibility but is not used to spawn a process. Leave Arg1 empty unless you intend to pass a valid ExifTool option: it is prepended verbatim and a mistaken value can be interpreted as a file argument.

The Perl library layout segment is defined in perlversion.go (perl5Lib).

Index

Constants

This section is empty.

Variables

View Source
var Arg1 string

Arg1, if non-empty, is prepended to every ExifTool argv built by Command/CommandContext and by NewServer. It must be a valid ExifTool argument (for example a global option). Do not set it to a host path unless that path is intended as an input file for ExifTool.

View Source
var Config string

Config, if non-empty, is passed as "-config" and the path value to ExifTool.

View Source
var Exec = "exiftool"

Exec is retained for compatibility with the original go-exiftool API. This implementation does not execute an external program; the value is ignored.

Functions

func Command

func Command(stdin io.Reader, arg ...string) ([]byte, error)

Command runs a single ExifTool invocation. Host paths are visible read-only under "/" inside the sandbox and os.TempDir is mounted read-write for any side effects ExifTool needs to perform. stdin is forwarded to ExifTool's standard input; pass nil if no input is required.

It uses context.Background; for cancellation see CommandContext.

func CommandContext

func CommandContext(ctx context.Context, stdin io.Reader, arg ...string) (out []byte, err error)

CommandContext is like Command but respects context cancellation. If ctx is already done when the call begins, it returns ctx.Err immediately.

func Unmarshal

func Unmarshal(data []byte, m map[string][]byte) error

Unmarshal parses line-oriented ExifTool text output: each line must contain a key, the substring ": ", and a value (covers the default format and -s / short output). It does not parse JSON (-json), XML (-X), or other structured outputs.

Values stored in m are subslices of data; if data is reused or modified after the call, entries in m may change. Copy with append([]byte(nil), v...) when retaining values past the lifetime of data.

Types

type Server

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

Server is a persistent, in-process ExifTool instance using the -stay_open protocol. It amortises the one-time Perl/ExifTool startup cost across many Server.Command calls. Concurrency safety: Command calls are serialised internally; lifecycle methods (Close, Shutdown) may be called concurrently.

Use NewServer to create an instance and call Server.Shutdown or Server.Close when finished to release resources.

func NewServer

func NewServer(commonArg ...string) (*Server, error)

NewServer starts a persistent ExifTool process using -stay_open true. commonArg is prepended to every subsequent Server.Command invocation (for example "-fast", "-api", "LargeFileSupport=1"). The package-level Arg1 and Config variables are also included. Call Server.Shutdown or Server.Close when done to release resources.

func (*Server) Close

func (e *Server) Close() error

Close forcibly stops the server by closing all I/O pipes. It does NOT wait for the eval goroutine to finish — this is necessary because Xzeroperl_eval is synchronous native code that may be in an uninterruptible computation phase (e.g. Perl module loading) where it is not reading from stdin. The goroutine will exit on its next failed I/O attempt. It is safe to call Close multiple times.

func (*Server) Command

func (e *Server) Command(arg ...string) ([]byte, error)

Command sends one ExifTool command via the stay_open protocol and blocks until the response (delimited by the {ready} token) is received. If ExifTool writes to stderr, it is returned as an error and the response is nil. On any I/O failure the server restarts automatically.

func (*Server) Shutdown

func (e *Server) Shutdown() error

Shutdown performs a graceful shutdown: it sends the -stay_open false signal, closes stdin, and waits for the eval goroutine to complete before releasing resources.

This is safe ONLY when the server has completed initialization and Perl is in the -stay_open read loop. If Perl is still initializing (loading modules, not reading stdin), this will block indefinitely because Xzeroperl_eval is synchronous native code that cannot be interrupted.

For a force-quit that works at any point in the server lifecycle, use Server.Close instead.

Returns an error if called after the server is already closed.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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