lightroom-py
Unofficial Python library, CLI, and Claude/Codex agent skill for automating Adobe Lightroom Classic
- Stars
- 1
- Language
- Python
- Created
- Apr 30, 2026
- Updated
- Sep 5, 2026
Introduction
lightroom-py
Drive Adobe Lightroom Classic from Python and Claude. First open agent driver with verified programmatic mask creation (35.4% pixel-diff confirmed) and the AI mask compute path documented elsewhere as impossible.
62 bridge handlers · 80 CLI verbs · 101 tests · 7+ real-LR validation sessions documented in CHANGELOG.md. Unofficial, MIT, not affiliated with Adobe.
Install — 3 user actions
pip install lightroom-py # 1. install
lightroom setup # 2. one-command: plugin + LaunchAgent + skill
# 3. In LR: Plug-in Manager → enable
# "lightroom-py bridge" (~10 sec, one-time)
lightroom setup installs the LR plugin into ~/Library/Application Support/Adobe/Lightroom/Modules/, generates a bridge token at ~/.lightroom/profiles/default/bridge.json, registers a macOS LaunchAgent that auto-starts the bridge server on login (no terminal), and installs the Claude agent skill. The token auto-loads into LR's plugin — no manual paste.
Verify with lightroom doctor.
Install variants + macOS TCC gotcha + Windows notes
Variants
| Need | Command |
|---|---|
| Default (CLI + Python lib) | pip install lightroom-py |
| With Claude Desktop MCP server | pip install "lightroom-py[mcp]" |
| With ExifTool fast-path for XMP | pip install "lightroom-py[exiftool]" |
| Everything (mcp + exiftool + dev) | pip install "lightroom-py[all]" |
macOS TCC-protected venvs
LaunchAgents on macOS can't read files under ~/Documents, ~/Desktop, ~/Downloads, ~/Pictures, ~/Movies, ~/Music without Full Disk Access. Install your venv outside those dirs:
python3 -m venv ~/.lightroom/venv
~/.lightroom/venv/bin/pip install lightroom-py
~/.lightroom/venv/bin/lightroom setup
lightroom setup and bridge install-service detect protected paths and refuse with workaround instructions before silently failing.
Windows
Manual bridge start (no LaunchAgent equivalent yet):
pip install lightroom-py
lightroom bridge install
lightroom bridge start # leave running
# Then in LR: Plug-in Manager → enable
What you can do
| Surface | Verbs |
|---|---|
| Catalog | catalog open / info / stats — counts of photos / folders / keywords / collections |
| Photos | photos list / count / find-by-path — filter by rating, camera, lens, keyword, date, color label, file format, ISO, aperture, focal length, GPS; SQLite read fast-path (50k+ catalogs return instantly) |
| Selection + nav | select, select-all/none/inverse/extend, next, previous |
| Flags + ratings | flag-pick/reject/clear, rate-up/rate-down, color-cycle |
| Metadata | add-keywords (hierarchical "People|Family|Mom"), rate, color, set-iptc (caption/title/headline/copyright/creator/city/state/country) |
| XMP | metadata write-xmp / read-xmp + ExifTool fast-path |
| Develop — globals | apply-preset, apply-settings, paste-settings --subset, get-settings, copy, reset, live set |
| Develop — typed | crop, hsl, color-grade, transform, lens-correction, calibration, detail, effects |
| Develop — tone curve | curve get/set/preset/linear/s-curve — RGB + per-channel |
| Develop — snapshots | snapshot create/list — safety checkpoints before agent edits |
| Develop — process version | process-version get/set |
| Develop — targeted resets | reset-crop / -masking / -spot / -redeye / -transforms |
| Masks — read + clear | mask list / clear (unified MaskGroupBasedCorrections schema, LR 15.3+) |
| Masks — create ⭐ | mask create-radial (verified end-to-end) + create-linear (best-effort) with 20+ Local* adjustments per mask |
| AI mask compute ⭐ | apply-preset --folder "Adaptive: Subject" + library export → LR's "AI Updates Required" dialog → one click → mask renders (verified 20.91% pixel-diff) |
| Collections | list / create / add / remove / delete / get-photos — regular + smart |
| Library | list-folders, export (JPEG/TIFF/PSD/DNG with watermark, output sharpening, resize, DPI, filename templates, minimize-metadata), make-virtual-copy, stack |
| Edit-In | export to disk, run for full roundtrip (export → external tool → reimport as stack) |
| Dev tools | bridge reload (hot-reload Lua handlers without LR restart), bridge eval (arbitrary Lua), bridge tail-log |
| Service mgmt (macOS) | bridge install-service / uninstall-service / service-status |
What's NOT implementable (Adobe SDK gaps, documented)
| Surface | Why |
|---|---|
| AI Denoise compute | LR exposes no public trigger for Enhance → Denoise…. Stage settings, user clicks Enhance. |
| Brush mask creation | Mask/Paint schema (Dabs: stroke array) not yet probed. Use radial as workaround for v0.6. |
| Spot removal create | Not exposed in LR 15.3. |
| Photo deletion | cat:trashPhotos doesn't exist in 15.3. |
| Virtual copy delete | Only creation works. |
| Lightroom Cloud (LR CC) | Partner-API gated; we target LR Classic. |
Command cheatsheet
Full reference: docs/cli-reference.md. The 20 most common patterns:
# === Setup + health ===
lightroom setup # one-shot: plugin + service + skill
lightroom doctor # diagnose install + bridge state
lightroom bridge ping # round-trip test
# === Catalog + cull ===
lightroom catalog open ~/Pictures/Lightroom/MyCatalog.lrcat
lightroom photos list --rating ">=4" --iso ">=400" --since 2026-01-01 --json
lightroom photos count --keyword wedding
lightroom photos select-all
lightroom photos flag-pick --selection
lightroom photos rate-up --selection # cycle +1 star
# === Metadata ===
lightroom metadata add-keywords "wedding,bride,People|Family|Mom" --selection
lightroom metadata rate 5 --selection
lightroom metadata set-iptc -f caption="Sunset" -f city="Paris" --selection
# === Global develop ===
lightroom develop list-presets
lightroom develop apply-preset "Pop" --folder "Adaptive: Subject" --selection
lightroom develop apply-settings '{"Exposure2012":0.5,"Contrast2012":20}' --selection
lightroom develop hsl --saturation orange=10 --luminance blue=15 --selection
lightroom develop color-grade --shadow-hue 215 --shadow-sat 20 \
--highlight-hue 30 --highlight-sat 25 --selection
lightroom develop curve preset "Medium Contrast" --selection
# === ⭐ Geometry mask creation (v0.6, verified) ===
lightroom develop mask create-radial --left 0.05 --right 0.5 \
--top 0.4 --bottom 0.95 --exposure 1.0 \
--name "subject-brighten" --selection
lightroom develop mask create-radial --top 0.15 --bottom 0.85 \
--left 0.15 --right 0.85 --invert --exposure -1.0 --feather 70 --selection
# ↑ vignette-style darkening (invert = effect OUTSIDE the ellipse)
# === Safety + experimentation ===
lightroom develop snapshot create "Pre-agent-edit" --selection
lightroom develop mask list --selection # see what masks are on the photo
lightroom develop mask clear --kind all --selection # wipe all masks
lightroom develop reset --selection # full reset
# === Production export ===
lightroom library export ~/Desktop/finals --selection --format JPEG \
--quality 88 --resize-long-edge 1920 --sharpening standard --dpi 96
# === External tool roundtrip ===
lightroom edit-in run "magick {input} -auto-level {output}" --selection
Three ways to use
1. CLI
lightroom photos list --rating ">=4" --iso ">=400" --json
lightroom develop apply-preset "Pop" --folder "Adaptive: Subject" --selection
lightroom develop mask create-radial --left 0.05 --right 0.5 --top 0.4 --bottom 0.95 \
--exposure 1.0 --selection # ⭐ verified renders
lightroom library export ~/finals --selection --format JPEG \
--resize-long-edge 1920 --sharpening standard --dpi 96
2. Python API
import asyncio
from lightroom import LightroomClient
async def main():
async with LightroomClient.connect() as lr:
await lr.catalog.open("~/Pictures/Lightroom/MyCatalog.lrcat")
keepers = await lr.photos.list(rating_gte=4, iso_gte=400, since="2026-01-01")
for p in keepers:
await lr.develop.apply_preset("Pop", folder="Adaptive: Subject", photo_uuids=[p.uuid])
await lr.develop.mask_create_radial(
left=0.05, right=0.5, top=0.4, bottom=0.95,
exposure=1.0, photo_uuids=[p.uuid],
)
asyncio.run(main())
Full async sub-client reference: docs/python-api.md.
3. Claude / agent skill
The package ships a canonical SKILL.md installable via lightroom skill install (also done automatically by lightroom setup). It activates on /lightroom or intent like "cull these photos", "apply my warm preset to the selection", "export the 5-star photos as JPEGs".
For Claude Desktop's MCP integration: pip install "lightroom-py[mcp]" + point Claude Desktop's config at the bundled lightroom-mcp binary. See docs/mcp.md.
4. DeepSeek Harness (dsh)
This repository is also a DeepSeek Harness plugin bundle (package.json + cordis.patch.yml at the root — pure configuration, no build step):
pip install "lightroom-py[mcp]" # the MCP server (0.6.1+; on 0.6.0 add "mcp<2")
dsh plugin add github:drshy-org/lightroom-py # the bundle; tools appear as mcp__lightroom_py__*
All 15 MCP tools become native dsh tools; the agent skill is shipped as dsh/skills/lightroom/SKILL.md. Details and the skill-discovery note: dsh/README.md. Any other MCP client (Codex CLI, Cursor, Gemini CLI, OpenCode) works the same way — point it at the lightroom-mcp binary.
Why this exists
LR Classic is the most locked-down RAW editor for automation: no AppleScript, no COM, no UXP. The Lua plugin SDK is the only door, and LrSocket / LrHttp are outbound-only — a plugin physically cannot host a server. lightroom-py is the missing Python-side counterpart: a tiny .lrplugin polls a local Python HTTP server, and Python (or Claude via the bundled agent skill / MCP server) drives Lightroom by enqueueing commands.
Claude Desktop / Code / CLI ──HTTP──▶ lightroom-py (Python aiohttp)
▲
│ poll / respond
│
.lrplugin ──────┘ (Lua, runs inside LR)
Configuration
| Variable | Default | Purpose |
|---|---|---|
LIGHTROOM_HOME | ~/.lightroom | Config root |
LIGHTROOM_PROFILE | default | Per-agent isolation |
The bridge server reads host / port / token from $LIGHTROOM_HOME/profiles/$LIGHTROOM_PROFILE/bridge.json — auto-generated on first lightroom setup. bridge.json is the single source of truth; the LR plugin reads it directly.
Troubleshooting
| Symptom | Fix |
|---|---|
doctor says "Bridge plugin not installed" | lightroom setup |
doctor says "loaded but not currently running" + last_exit=256 | macOS TCC blocked the venv. Reinstall outside ~/Documents/. |
bridge ping times out | lightroom bridge install --force && lightroom bridge reload. For Lua changes outside Handlers.lua: Cmd+Q LR + relaunch. |
| Plugin disabled after a Lua error | LR auto-disables on plugin init crash. File → Plug-in Manager → re-enable. |
Yielding is not allowed within a C or metamethod call | Use LrTasks.pcall instead of bare pcall. See CHANGELOG v0.1.2 / v0.3.1 / v0.4.1. |
| AI mask preset applied but export looks unchanged | Click Export on the "AI Updates Required" modal with the "Update affected photos" box checked. |
For the developer log: lightroom bridge tail-log -n 50.
Development
git clone https://github.com/drshy-org/lightroom-py.git
cd lightroom-py
python -m venv .venv && source .venv/bin/activate
pip install -e ".[all]"
pre-commit install
ruff format . && ruff check . && mypy && pytest
See CONTRIBUTING.md for the ranked backlog, architecture details, the LR sandbox gotchas list, and the hot-reload dev loop.
Roadmap
| Version | Status |
|---|---|
| v0.1–v0.3 | Scaffold, bridge protocol, SQLite read, metadata writes, Collections/Library/Edit-In, MCP server, hot-reload tooling |
| v0.4 | Feature catch-up: tone curve, snapshots, process version, paste-settings, mask read/clear, AI staging |
| v0.4.2 | Install simplification — setup, LaunchAgent, token auto-load, TCC defensive check |
| v0.5 | Typed Develop wrappers, EXIF query layer, production exports |
| v0.6 (current) | Geometry mask CREATION (radial verified). Unified MaskGroupBasedCorrections mask_list rewrite. |
| v0.7 | Brush mask schema probe + create, linear mask empirical verification, Windows LaunchAgent, .mcpb Claude Desktop bundle |
| Out of scope | LR Cloud, AppleScript (LR doesn't expose), photo deletion + virtual-copy delete + AI compute trigger (SDK gaps) |
Contact
Built by drshy — drshy.xyz. Issues + PRs at github.com/drshy-org/lightroom-py. If lightroom-py saves you time, a star ⭐ is appreciated.
Citation
If you use lightroom-py in research, blog posts, papers, or talks, please cite it. GitHub auto-detects CITATION.cff and shows a "Cite this repository" button on the right sidebar.
Plain-text / APA:
drshy. (2026). lightroom-py: A Python and Claude agent driver for Adobe Lightroom Classic (v0.6.0) [Software]. Retrieved from https://github.com/drshy-org/lightroom-py — http://www.drshy.xyz
BibTeX:
@software{drshy_lightroom_py_2026,
author = {drshy},
title = {{lightroom-py}: {A} {Python} and {Claude} agent driver for {Adobe} {Lightroom} {Classic}},
url = {https://github.com/drshy-org/lightroom-py},
version = {0.6.0},
date = {2026-05-10},
note = {Author homepage: \url{http://www.drshy.xyz}}
}
If your work uses one of the specific empirical results from this project (the verified geometry mask creation pixel-diff, the AI mask compute path, the LR Classic 15.3 sandbox gotchas catalog), please link to the relevant CHANGELOG.md version entry so readers can find the original evidence.
License
MIT — see LICENSE. Use freely; no warranty; not affiliated with Adobe.