Skip to content

libvips · ExifTool · --dry-run everywhere · crash-safe undo

mediakit

Bulk media chores. Previewable, atomic, reversible.

One single-binary CLI to inspect, rename, clean, convert, resize, and dedup images in bulk — wrapping libvips and ExifTool behind one safe, scriptable interface.

Bulk chores, without the footguns

Five core principles, and the two safety mechanisms that make them true.

Wraps engines, never reimplements them

Pixels go to libvips, metadata to ExifTool — the fidelity oracle for MakerNotes, XMP, and ICC. mediakit is the safe orchestration layer, not a new codec.

Preview before you commit

--dry-run is global: see every resolved source→target mapping before a byte moves. It is a lower bound on failure, never a promise of success.

Crash-safe undo journal

An append-only, fsync'd JSONL journal is written before every mutation, so mediakit undo --last rolls a batch back — even after a crash mid-run.

Atomic, non-clobbering writes

Temp file on the same filesystem → fsync → rename. No-clobber by default, and an in-place edit preserves the file's exact pre-existing mode.

Signals, not verdicts

Perceptual hash, blur, colorfulness, entropy, clipping — raw scalars with documented, corpus-relative thresholds. calibrate tunes them against your own corpus.

Scriptable by design

Every read path emits -o table|json|ndjson. Compose verbs into a versioned run recipe, or let the watch daemon apply it to a directory's future arrivals.

Every destructive path runs the same spine

One planner, one journal, one undo — no verb writes files on its own.

discovery

Magic-byte kind detection; the reserved .mediakit-* temp namespace is never a source

planner

Resolves every source→target up front, so --dry-run is exact and collisions are global

executor

A bounded errgroup worker pool over the planned set

safety + journal

Temp on the same filesystem → fsync → rename, no-clobber by default, journalled before the write

engines

libvips for pixels · ExifTool for metadata — wrapped as L4 ports, never reimplemented

L1 CLI (cobra) → L2 handlers → L3 core → L4 engine ports → L5 engines. Core never imports the CLI layer; new verbs and heuristics register through the operation registry, never by editing the executor.

Twelve verbs, one safety model

Every read path emits a table and JSON. Every destructive path is previewable, atomic, and reversible.

inspect

Dimensions, format, content SHA-256, every ExifTool metadata group — plus opt-in heuristics and AI-provenance evidence.

rename

Token-template bulk rename — {date}, {counter}, {camera}, {w}/{h}, {hash} — one global counter, collision auto-suffixed.

clean

Strip metadata: everything, selectively by tag or group, or just GPS. ExifTool-backed and orientation-safe.

convert

Reformat between JPEG/PNG/WebP/AVIF/HEIC with quality targets and explicit --icc assign|convert|strip.

resize

--resize, --fit, --crop, --rotate, --flip via libvips shrink-on-load. --fit is downscale-only.

dedup

Cluster near-duplicates by perceptual-hash Hamming distance. Strictly read-only — it reports, it never deletes.

meta

set, remove, copy (ExifTool's native -tagsFromFile), and apply from a YAML manifest.

calibrate

Survey heuristic distributions across a corpus and report percentiles — corpus-relative tuning, never a verdict.

run

Compose verbs into a versioned YAML recipe: one preview, one confirmation, one journal batch, one undo.

watch

An fsnotify ingest daemon that applies a recipe to a directory's future arrivals, settling each file first.

undo

Roll back journalled batches — --last, --batch <id>, --list, --gc.

doctor

Engine detection, versions, and the real format-support matrix with per-OS install hints. Exits 3 when your install is incomplete.

config and completion round out the surface — run mediakit --help for the whole tree. Video (video probe / transcode / frames / clean) is Phase 2, parked: additive on the same spine, deliberately not queued.

Quick Start

Runtime engines aren't vendored — mediakit shells out to them, anddoctoris the single source of truth for "is my install complete".

# Debian / Ubuntu
sudo apt install libimage-exiftool-perl libvips-tools
# macOS
brew install exiftool vips
terminal
# Build the static binary into bin/ (pure Go, CGO disabled)
make build

# Verify the runtime engines are present (libvips + ExifTool for v1)
./bin/mediakit doctor

# Inspect an image, add low-level heuristics, emit JSON
./bin/mediakit inspect photo.jpg --heuristics -o json

# Preview a bulk rename before touching anything (dry-run is global)
./bin/mediakit rename ./shoot -r --template '{date:2006-01-02}_{counter:04}' --dry-run

# Run the tests (unit + golden + testscript e2e; engine tests self-skip if absent)
make test

v0.1.0 is tagged and released, but the source repository is still private — the release tarballs, the .deb/.rpm packages, and the GHCR image all sit behind that same access control, andgo install github.com/ArmandoHerra/mediakit/cmd/mediakit@latestwill not resolve without it. Building from source is the canonical install path today. Full detail in theREADME's Installation section.

A stable exit-code contract

The same contract mediakit --help prints — a host with the binary and no network can discover it from the tool.

0

success

1

partial failure (some bulk items failed)

2

usage error

3

required engine missing or too old

4

no files matched

5

aborted (declined confirmation / --fail-fast)

Technology Stack

Every version below, straight fromthe README's Tech Stack table.

TechnologyVersionPurpose
Go1.25+ (toolchain 1.26)language/runtime
spf13/cobra1.10CLI command tree
spf13/viper1.21config file + MEDIAKIT_* env + profiles
golang.org/x/sync0.22bounded worker pool (errgroup.SetLimit)
golang.org/x/image0.45WebP/TIFF decoders for the engine-free dimension probe and dedup's full decode
corona10/goimagehash1.1perceptual hashing (aHash/dHash/pHash)
davidbyttow/govips/v22.18optional in-process libvips (-tags govips)
rogpeppe/go-internal1.14testscript (txtar) CLI e2e tests
libvips (runtime)≥ 8.14pixel engine (default: vips CLI subprocess)
ExifTool (runtime)≥ 12.60metadata engine