Recipes¶
Short, task-oriented examples you can copy and adapt. Each one builds on the flags documented in the CLI reference. For the full option list and defaults, see that page.
Process a folder of RAW + JPEG recursively¶
Scan a directory tree for both Canon RAW and JPEG files and tag everything found.
The default --extensions is cr3,jpg, so --extensions is only needed when you want a different
set. Matching is case-insensitive.
Tag a few explicit files and overwrite keywords¶
Point at individual files (repeat -i) and replace any keywords already present instead of merging.
By default photo-tagger preserves existing keywords. --overwrite-keywords discards them and keeps
only what the model proposes.
Embed metadata into JPEGs¶
Write the title, description, and keywords into the image files themselves rather than into XMP sidecars.
Warning
--embed-in-photo modifies your originals. ExifTool keeps a *_original backup by default; add
--no-backup-xmp only if you are sure you do not need it.
Point at a remote Ollama host with a custom model¶
Use an Ollama server on another machine and a specific vision-language model.
photo-tagger \
-i ~/Pictures/inbox \
--provider ollama \
--url http://gpu-box.local:11434/v1 \
--model qwen3-vl:32b
Tip
Prefer environment variables for secrets. Set OLLAMA_BASE_URL and OLLAMA_API_KEY instead of
passing --url and --api-key on the command line.
Dry-run preview¶
See the metadata the model would write without changing any files.
--dry-run still runs the model (and uses any cache), but writes neither sidecars nor embedded
metadata.
Process concurrently and write a run summary¶
Run several photos at once with a thread pool and save a JSON summary when the run finishes.
photo-tagger \
-i ~/Pictures/inbox \
--recursive \
--workers 4 \
--summary-file ~/Pictures/run-summary.json
The model server is usually the bottleneck, so raising --workers helps most when the server can
serve requests in parallel. The summary file records success and failure counts, failed files, token
usage, and wall time.
Export a per-photo CSV report¶
Write one CSV row per photo with everything extracted and computed: the generated title, description, and keywords, the metadata already on the file, the camera/location EXIF, and per-photo token usage and timing.
Rows stream as each photo finishes, so a run stopped with Ctrl-C still leaves a valid file. Pair it
with --dry-run to preview a batch as a spreadsheet without writing any metadata, or with --json
to get both the CSV and the NDJSON stream from one run.
Custom prompt with a keyword cap¶
Replace the default user prompt and limit how many AI keywords are kept per photo.
Existing photo metadata (location, GPS, camera EXIF) is still appended to your custom prompt
automatically. --max-keywords caps the AI keywords before they are merged with existing ones.
Cache model outputs across reruns¶
Store model outputs in a SQLite cache so reruns skip the model call when nothing relevant changed.
The cache key combines a hash of the image data (ExifTool's ImageDataHash, which ignores metadata)
with the model name and sampling settings, so changing the model or temperature invalidates stale
entries. Because the hash ignores metadata, a rerun still hits the cache even when the first pass
embedded tags into the photo with --embed-in-photo. The file is created if missing and safe to
delete.
Resume a killed run¶
Track successes in a skip file, then resume from it after an interruption. Use the same path for both flags so the next run skips what already succeeded.
photo-tagger \
-i ~/Pictures/inbox \
--recursive \
--skip-from ~/Pictures/done.txt \
--append-to-skip-file ~/Pictures/done.txt
--append-to-skip-file adds each successfully tagged filename as the run progresses, and
--skip-from drops those filenames on the next run. The file uses one filename per line; lines
starting with # are comments.
Tag a mixed folder, skipping already-tagged files¶
In a folder where some images are already tagged, process only the ones that still need metadata.
--skip-tagged drops files that already have keywords, a title, or a description in the image or
its XMP sidecar.
Filter by date and stream NDJSON into jq¶
Tag only files modified within a date range and pipe one NDJSON line per photo into jq.
photo-tagger \
-i ~/Pictures/library \
--recursive \
--newer-than 2026-01-01 \
--older-than 2026-06-01 \
--json \
| jq -r 'select(.status == "ok") | "\(.file)\t\(.title)"'
--newer-than drops files whose mtime is on or before the timestamp; --older-than drops files on
or after it. Logs and the progress bar stay on stderr, so stdout pipes cleanly into jq.
Refuse concurrent runs with a lock file¶
Guard a scheduled or shared job so a second photo-tagger cannot start while one is already running.
The lock is acquired before processing starts. If another process already holds it, photo-tagger refuses to start. This works on Linux, macOS, and Windows.