No description
  • Clojure 97.6%
  • Makefile 1.3%
  • Swift 1.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Ryan Gapac 2f5b5a47f8 refactor: one fake runner instead of four — shared test-support module
test-support module: fake-tools builder encodes the tools seam
argument/output protocol once — auge single and --ndjson shapes,
apfel, apfel-tag, ffmpeg, ffprobe, tags binary, which/swiftc. A flag
change in tools.clj now updates one fake, not one per test file.
Closes architecture-review candidate 2.

- all per-file op-dispatch fakes deleted; specs read as data
- shared temp-dir / write-file! / with-temp-dir / fake-run /
  capturing-run replace byte-identical local copies
- ndjson map handlers emit one JSON line per stdin path (the real
  protocol); fn handlers remain for per-path variance
- unspecified programs fail loud so protocol drift fails tests
- assertion content unchanged; 80 tests / 178 assertions green
2026-08-09 13:21:03 +08:00
mac/tags feat: skip already-tagged files by default; -a/--all reprocesses 2026-07-31 09:12:41 +08:00
src/clj_apt_reaction_image refactor: name the naming seam — deep naming module behind one interface 2026-08-09 13:10:59 +08:00
test/clj_apt_reaction_image refactor: one fake runner instead of four — shared test-support module 2026-08-09 13:21:03 +08:00
.gitignore feat(mac): add native app shell and json backend contract 2026-03-26 07:35:56 +08:00
CONTEXT.md refactor: name the naming seam — deep naming module behind one interface 2026-08-09 13:10:59 +08:00
deps.edn renamed 2026-03-25 23:02:47 +08:00
Makefile feat: organise clips (gif/video) via deterministic ffmpeg frame sampling 2026-07-29 09:36:50 +08:00
README.md feat: skip already-tagged files by default; -a/--all reprocesses 2026-07-31 09:12:41 +08:00

clj-apt-reaction-image

On-device reaction image organiser — rename and tag your maymays using Apple's built-in Vision and Foundation Models.

No cloud. No API keys. No Ollama. 100% on-device.

Tested on my M5 MacBook on 400+ images. Works.

What it does

Walks a directory of images and clips (GIFs, mp4/mov/m4v/webm/mkv videos), then for each one:

  1. Frame sampling — ffmpeg extracts N still frames for analysis (clips only; default 6)
  2. OCR — extracts visible text (auge --ocr)
  3. Classify — identifies objects in the scene (auge --classify)
  4. Detect — counts faces (auge --faces)
  5. Tag — generates Finder tags for Spotlight search (apfel-tag)
  6. Rename — creates a short kebab-case filename (apfel)
  7. Apply — renames the file and sets Finder tags
# Before:     distracted-bf.jpg   doge.jpg   screenshot.png
# After:      people-adult-clothing-jacket-c.jpg  corgi-dog.jpg  telegram-edit-view-window.png

Requirements

  • macOS 26+ (Tahoe) on Apple Silicon
  • Homebrew
  • Apple Intelligence enabled in System Settings

Install

# Install on-device tools
make install

# Compile the Finder-tag helper
make compile

Usage

Dry run (preview changes without touching files):

make run IMAGES_DIR="~/iCloud/Pictures/maymays" ARGS="--dry-run"

Actually rename and tag:

make run IMAGES_DIR="~/iCloud/Pictures/maymays"

Or run directly with Clojure:

clojure -M:run organize --images-dir "~/iCloud/Pictures/maymays" --dry-run

Re-runs skip finished files. A file that already carries Finder tags is treated as categorized and skipped, so re-running on the same directory only picks up new additions. Force a full reprocess with -a / --all:

clojure -M:run organize --images-dir "~/iCloud/Pictures/maymays" --all

JSON output for scripting:

clojure -M:run organize --images-dir "~/iCloud/Pictures/maymays" --output json --dry-run

More frames for dense clips:

clojure -M:run organize --images-dir "~/iCloud/Videos/gifs" --frames 12 --dry-run

How it works

Step Tool What it produces
Frame sampling ffmpeg N still frames from each clip (default 6)
OCR auge --ocr visible text from the image
Classification auge --classify object/scene labels (top 5)
Face detection auge --faces number of faces
Content tagging apfel-tag Finder tags for search
Smart naming apfel (Apple Foundation Model) short kebab-case filename
Read/set tags mac/tags/main.swift reads and writes Finder tags via xattr

All tools run Apple's on-device frameworks — Vision (VNRecognizeTextRequest, VNClassifyImageRequest, VNDetectFaceRectanglesRequest) and FoundationModels.

Tests

make test
# or: clojure -M:test

Project structure

├── Makefile                     — install, compile, run, test
├── deps.edn                    — Clojure deps (just data.json)
├── src/
│   └── clj_apt_reaction_image/
│       └── core.clj            — pipeline orchestrator
├── mac/
│   └── tags/
│       └── main.swift          — Finder tag helper
└── test/
    └── clj_apt_reaction_image/
        └── core_test.clj       — unit tests

Caveats

  • Apple Intelligence must be enabled in System Settings for apfel and apfel-tag.
  • Safety guardrails on Apple's Foundation Model may block images with people, falling back to label-based naming.
  • Images with visible text (screenshots, memes, chat images) get the best filenames.
  • Plain reaction images without text get descriptive-but-generic names from classification labels.
  • Clips are judged by sampled frames only — a punchline that appears between samples can be missed (raise --frames for dense clips).
  • webm and mkv have no native macOS support and are handled entirely through ffmpeg frame extraction.