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)
librawsupport forrawpy(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 (defaulthttp://localhost:11434/v1)OLLAMA_API_KEY– optional API key passed to Ollama requestsLM_STUDIO_BASE_URL– override the LM Studio endpoint (defaulthttp://localhost:1234/v1)LM_STUDIO_API_KEY/OPENAI_API_KEY– API key for LM Studio’s OpenAI-compatible serverMODEL_NAME– default model name (defaultqwen3-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 (defaultcr3,jpg)-r/--recursive– recurse into subdirectories while scanning inputs-m/--model– model identifier understood by your provider--provider–ollamaorlmstudio(defaults tolmstudio)--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*_originalsnapshot 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e15a208a1aac3d60866ae2dc2a3fbb96a9961068de39cca76e7af42d80098b3
|
|
| MD5 |
7d208ba6a047aee2ba055033d5bdcb14
|
|
| BLAKE2b-256 |
c745f2917692830f09796c112cda5cee654651c755c1ea0b7f51ac7722702431
|
Provenance
The following attestation bundles were made for photo_tagger-0.1.0.tar.gz:
Publisher:
publish.yml on jbsilva/photo-tagger
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
photo_tagger-0.1.0.tar.gz -
Subject digest:
3e15a208a1aac3d60866ae2dc2a3fbb96a9961068de39cca76e7af42d80098b3 - Sigstore transparency entry: 955601167
- Sigstore integration time:
-
Permalink:
jbsilva/photo-tagger@b12055d9e45f02c354fd80001a198b515dd0855c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/jbsilva
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b12055d9e45f02c354fd80001a198b515dd0855c -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0135bc6a970d7c1d554aaeec5fa4e9b624cb3112dd1ec41e10531204212d789d
|
|
| MD5 |
852a45e43d92474ed901030286fbaf44
|
|
| BLAKE2b-256 |
8260b95c05d97f601fd50e03a74a1defc3bd18d296651f5a1c42b447ca285c67
|
Provenance
The following attestation bundles were made for photo_tagger-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on jbsilva/photo-tagger
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
photo_tagger-0.1.0-py3-none-any.whl -
Subject digest:
0135bc6a970d7c1d554aaeec5fa4e9b624cb3112dd1ec41e10531204212d789d - Sigstore transparency entry: 955601178
- Sigstore integration time:
-
Permalink:
jbsilva/photo-tagger@b12055d9e45f02c354fd80001a198b515dd0855c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/jbsilva
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b12055d9e45f02c354fd80001a198b515dd0855c -
Trigger Event:
release
-
Statement type: