Skip to main content

CLI tool to describe photos and add keywords using AI

Project description

Photo Tagger

Photo Tagger is a command-line helper that asks a vision-language model to analyze your photos and writes Lightroom-compatible metadata.

By default it keeps your originals untouched by creating XMP sidecars, but you can embed the updates directly into each photo with --embed-in-photo.

Highlights

  • Works with RAW and standard image formats (CR3, CR2, NEF, JPG, PNG, and more)
  • Generates a title, a concise description, and hierarchical keywords
  • Merges with existing metadata unless you opt-in to overwrite
  • Supports Ollama and LM Studio compatible OpenAI APIs
  • Converts images to compact JPEG bytes to minimize token usage
  • Generates detailed log files for easy debugging and auditing
  • Highly configurable via CLI flags and environment variables

Requirements

  • Python 3.14+
  • ExifTool available on PATH
  • A running Ollama or LM Studio server exposing a vision-language model (for example Qwen-VL)
  • libraw support for rawpy (install via Homebrew on macOS: brew install libraw)

Installation

For end-users, the recommended installation method is via uv:

uv tool install photo-tagger

For development (tests, linting):

uv sync --group dev --group test

Configuration

Environment variables provide defaults so you can keep the CLI concise:

  • OLLAMA_BASE_URL – override the Ollama HTTP endpoint (default http://localhost:11434/v1)
  • OLLAMA_API_KEY – optional API key passed to Ollama requests
  • LM_STUDIO_BASE_URL – override the LM Studio endpoint (default http://localhost:1234/v1)
  • LM_STUDIO_API_KEY / OPENAI_API_KEY – API key for LM Studio’s OpenAI-compatible server
  • MODEL_NAME – default model name (default qwen3-vl:32b)
  • JPEG_DIMENSIONS, JPEG_QUALITY, TEMPERATURE, MAX_TOKENS, RETRIES – fine-tune runtime

Any CLI flag takes precedence over the environment.

Usage

The CLI is exposed as photo-tagger once installed, or you can invoke it directly:

photo-tagger -i ./photos --ext cr3,jpg -r

Key options:

  • -i/--input PATH – repeatable; mix files and directories
  • --ext – comma-separated extension list used when scanning directories (default cr3,jpg)
  • -r/--recursive – recurse into subdirectories while scanning inputs
  • -m/--model – model identifier understood by your provider
  • --providerollama or lmstudio (defaults to lmstudio)
  • --url / --api-key – override provider endpoint and credentials
  • --overwrite-keywords – replace instead of merge existing keyword metadata
  • --no-write-title / --no-write-description – skip writing those fields
  • --no-backup-xmp – avoid creating *_original snapshot before writing
  • --embed-in-photo – write metadata directly into the image instead of creating an XMP sidecar
  • --jpeg-dimensions, --jpeg-quality, --temperature, --max-tokens, --retries – control inference behavior

A successful run creates or updates an .xmp sidecar for every processed image (unless you embed the metadata). Existing metadata is merged so Lightroom keeps hierarchical keywords such as Animal|Bird|Osprey intact.

Examples

Process a folder of RAW and JPEG files recursively:

photo-tagger -i ~/Pictures/Portfolio --ext cr3,jpg -r

Tag a few explicit files and overwrite existing keywords:

photo-tagger \
  -i IMG_0001.CR3 \
  -i IMG_0002.CR3 \
  --overwrite-keywords

Embed metadata directly into a set of JPEGs:

photo-tagger -i ./exports --ext jpg --embed-in-photo

Send requests to a remote Ollama host with a custom model:

photo-tagger -i ./shoot --provider ollama --model llava:34b --url http://ollama-box:11434/v1

Logging

Logs are written to stderr and to a timestamped file (for example 20260101...-photo_tagger.log). Adjust levels with --console-log-level and --file-log-level, or disable either by setting the value to OFF.

Testing

Run the unit tests with:

pytest

If you plan to contribute, also run ruff check for linting before opening a PR.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

photo_tagger-0.1.0.tar.gz (18.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

photo_tagger-0.1.0-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

File details

Details for the file photo_tagger-0.1.0.tar.gz.

File metadata

  • Download URL: photo_tagger-0.1.0.tar.gz
  • Upload date:
  • Size: 18.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for photo_tagger-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3e15a208a1aac3d60866ae2dc2a3fbb96a9961068de39cca76e7af42d80098b3
MD5 7d208ba6a047aee2ba055033d5bdcb14
BLAKE2b-256 c745f2917692830f09796c112cda5cee654651c755c1ea0b7f51ac7722702431

See more details on using hashes here.

Provenance

The following attestation bundles were made for photo_tagger-0.1.0.tar.gz:

Publisher: publish.yml on jbsilva/photo-tagger

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file photo_tagger-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: photo_tagger-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for photo_tagger-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0135bc6a970d7c1d554aaeec5fa4e9b624cb3112dd1ec41e10531204212d789d
MD5 852a45e43d92474ed901030286fbaf44
BLAKE2b-256 8260b95c05d97f601fd50e03a74a1defc3bd18d296651f5a1c42b447ca285c67

See more details on using hashes here.

Provenance

The following attestation bundles were made for photo_tagger-0.1.0-py3-none-any.whl:

Publisher: publish.yml on jbsilva/photo-tagger

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page