Skip to content

Repository files navigation

imago

CI

A batch WebP renditions tool — point it at images and it derives resized WebP renditions per repeatable rules, maintains a measured-size JSON manifest, and skips everything that is already up to date. Think make for responsive images.

The third sibling of arbor (map files) and vestigo (find files): imago derives them.

Status: work in progress. The engine, CLI, and manifest contract below are built, tested (158 assertions, Windows + Linux, ASan/UBSan-clean), and in daily use; parallel encoding (--jobs) and per-cause drift reporting under --check are on the roadmap.

$ imago -r h1600:q80 -r h900:q78:suffix=-thumb -m assets/manifest.json -o assets/images gallery/
gallery/nymphae.png  encoded 2
gallery/dunes.jpg    encoded 1, skipped 1 (up to date)
2 files: 3 encoded, 1 skipped, 0 failed

Features

  • Rendition rules — repeatable -r/--rendition specs, :-separated and order-independent: exactly one bound (hN height, wN width, capN long edge), exactly one qN quality 0–100, optional suffix=<text> (the unsuffixed rule is the base rendition). Malformed or duplicated parts are refused before anything encodes.
  • Inputs — files and directories (a directory contributes its jpg/jpeg/png/webp files, non-recursively — imago walks, so no shell globbing is needed). EXIF orientation is honoured on JPEG load. -n/--name renames the output stem (exactly one input file).
  • Idempotent, rule-aware — the make model: an output at least as new as its source is skipped; -f/--force re-encodes. With a manifest, each entry also records the exact rule that produced it, so changing a recipe re-encodes even though the output is newer — make's classic command-change flaw, closed. --allow-upscale is part of that fingerprint for h/w rules (cap is shrink-only by nature). Never-upscale by default.
  • Atomic writes — every rendition goes to a sibling .tmp then an atomic replace (with a retry for sync clients briefly holding files): a crash or disk-full mid-encode can never leave a truncated output whose fresh mtime would fake "up to date" forever.
  • JSON manifest (-m/--manifest, optional) — upsert-merged by name: entries for the names encoded this run are replaced, every other name is preserved. Skipped outputs heal missing entries cheaply (a WebP header read, no decode). A corrupt manifest is refused, never overwritten. The manifest is the machine interface; stdout is for humans.
  • -c/--check — encodes and writes nothing (not even the output directory): reports what would encode (missing, stale, rule-changed, damaged) and exits 0 clean / 1 drift. The dry-run and the CI parity gate in one flag. Contradicts --force (refused).
  • Refusals before work — a malformed rule, a missing input, an unsupported extension, colliding output stems, an output that would land on any input's own path, or a corrupt manifest all exit 2 before a single pixel is decoded.
  • Exit codes — grep-style: 0 all work done (all-skipped counts), 1 at least one input failed (or drift under --check), 2 usage refusal. Per-rendition failure causes go to stderr.

Manifest shape

{
    "imago": "0.2.0",
    "images": {
        "nymphae": {
            "base":   { "rule": "h1600:q80", "width": 1243, "height": 1600, "bytes": 245120 },
            "-thumb": { "rule": "h900:q78",  "width": 699,  "height": 900,  "bytes": 84512 }
        }
    }
}

The base (unsuffixed) rendition stores under the reserved key "base" (suffix=base is refused). With a manifest configured, source stems must be identifiers ([A-Za-z0-9_-]) — rename the file, use --name, or drop the manifest.

Building

Requires a C23 compiler (gcc 14+ / clang 18+) and the codec libraries below. All CFW dependency sources are bundled under include/; libspng is bundled under third_party/.

make            # ./imago
make test       # build + run the unit suite (158 assertions)

or, with CMake:

cmake -B build && cmake --build build
ctest --test-dir build

Dependencies

imago links libjpeg-turbo ≥ 3 (it speaks the tj3* API), libwebp (+libsharpyuv), and zlib:

Platform Install
Windows (MSYS2 UCRT64) pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-libjpeg-turbo mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-zlib make
Debian / Ubuntu apt install libwebp-dev zlib1g-dev — plus libjpeg-turbo 3.x from source (Debian 13 / Ubuntu 24.04 still package 2.1.5, which lacks tj3*); the CI workflow shows the 6-line recipe
macOS (Homebrew) brew install jpeg-turbo webp zlib

Usage

# Two renditions of every image under gallery/, tracked in a manifest
imago -r h1600:q80 -r h900:q78:suffix=-thumb -m assets/manifest.json -o assets/images gallery/

# One file, renamed on output, with a long-edge cap for a zoom view
imago -r cap2800:q82:suffix=-zoom -n nymphae -o assets/images gallery/nymphae-final.png

# CI drift gate: report what would encode, write nothing, exit 1 on drift
imago --check -r h1600:q80 -m assets/manifest.json -o assets/images gallery/

# Force one file to re-encode even though its output is newer
imago -f -r h1600:q80 -o out photo.jpg

imago --help prints the full flag reference.

Repository notes

include/ vendors the needed modules of a private C framework (CFW) so the package is self-contained; reapply_local_edits.py documents this bundle's deliberate divergences from that upstream and must be re-run after any re-vendor. src/ holds the tool itself.

Roadmap

  • --jobs N parallel encoding (file-granular; measurement-gated).
  • Per-cause drift lines under --check (would encode 1 (rule changed)).
  • AVIF output and fit-within-box bounds are forward-compatible with the spec grammar.

License

MIT — see LICENSE.

About

Batch WebP renditions CLI - make-style rule-aware skips, atomic writes, measured-size JSON manifest, read-only drift checking

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages