macOS CLI tool that auto-labels iCloud Photos using any OpenAI-compatible LLM
Project description
iCloud Image Labeler
Auto-label your Apple Photos library using any OpenAI-compatible LLM. Generates keywords, titles, descriptions, and OCR text for photos and videos, then writes the metadata back into Photos.app.
Works with any OpenAI-compatible API — designed for LM Studio but also works with Ollama, vLLM, or cloud providers.
How It Works
Photos.app ──> Discovery ──> Export ──> LLM ──> Writer ──> Photos.app
(osxphotos) (Pillow) (API) (PhotoScript)
- Discovery — Queries the Photos library for media with no keywords set, filtered by date range and media type.
- Export — Exports photos as JPEG (with HEIC/iCloud fallbacks), extracts frames from videos via ffmpeg.
- LLM — Sends images to an OpenAI-compatible API. Returns structured JSON with keywords, title, description, and OCR text.
- Writer — Writes metadata back into Photos.app via PhotoScript (AppleScript automation).
Photos are processed in parallel (configurable thread count), videos sequentially.
Requirements
- macOS (requires Photos.app and AppleScript)
- Python 3.10+
- ffmpeg (for video frame extraction)
- An OpenAI-compatible LLM API (LM Studio, Ollama, etc.)
Installation
brew tap ziadalzarka/tap
brew install icloud-image-labeler
iCloud Settings
For best performance, go to System Settings → Apple Account → iCloud → Photos and enable Download Originals to this Mac. This downloads all photos in advance so the labeler doesn't have to wait for individual iCloud downloads during processing.
Quick Start
# Interactive setup — connects to your LLM and picks a model
icloud-image-labeler init
# Process all unprocessed media
icloud-image-labeler run
Usage
# Process 5 items from the last 30 days
icloud-image-labeler run --limit 5 --days 30
# Preview without writing (dry run)
icloud-image-labeler run --dry-run
# Photos only, skip videos
icloud-image-labeler run --no-video
# Process media from 60 to 30 days ago
icloud-image-labeler run --days 60 --to-days 30
# Process specific photos by UUID
icloud-image-labeler run --uuid ABC123 DEF456
# Run continuously (daemon-style loop)
icloud-image-labeler run --loop
# Use a specific model or API endpoint
icloud-image-labeler run --base-url http://localhost:1234/v1 --model my-model
# Debug logging
icloud-image-labeler run -v
All run Options
| Flag | Description |
|---|---|
--limit N |
Max items to process |
--days N |
Look back N days (0 = all time) |
--to-days N |
Skip most recent N days |
--photo / --no-photo |
Include/exclude photos |
--video / --no-video |
Include/exclude videos |
--write |
Force write metadata |
--dry-run |
Preview without writing |
--base-url URL |
LLM API endpoint |
--api-key KEY |
API key for LLM |
--model NAME |
Model identifier |
--threads N |
Parallel photo threads |
--video-frames N |
Frames to extract per video |
--uuid UUID [...] |
Process specific photo UUIDs |
--loop |
Run continuously with poll interval |
-v, --verbose |
Debug logging |
Configuration
Config is stored at ~/.image-labeler/config.json. Run icloud-image-labeler init for interactive setup, or manage directly:
icloud-image-labeler config show # View current config
icloud-image-labeler config set days 14 # Update a value
icloud-image-labeler config reset # Reset to defaults
icloud-image-labeler config path # Print config file path
If no config exists when you run icloud-image-labeler run, the setup wizard launches automatically.
| Key | Default | Description |
|---|---|---|
base_url |
http://localhost:1234/v1 |
OpenAI-compatible API endpoint |
api_key |
"" |
API key (optional, empty for local LM Studio) |
model |
qwen/qwen3.5-9b |
Model identifier |
days |
0 |
Look back N days (0 = all time) |
to_days |
0 |
Skip most recent N days |
limit_per_cycle |
0 |
Items per cycle (0 = all) |
threads |
4 |
Max parallel photo threads |
video_frames |
5 |
Frames to extract from videos |
max_dimension |
1024 |
Max image dimension sent to LLM (px) |
photo |
true |
Include photos in processing |
video |
true |
Include videos in processing |
write |
true |
Write metadata to Photos.app |
poll_interval |
300 |
Seconds between cycles in loop/daemon mode |
Daemon
Run as a background service that auto-starts on login via macOS Launch Agent:
icloud-image-labeler daemon start # Install and start Launch Agent
icloud-image-labeler daemon status # Check if running
icloud-image-labeler daemon restart # Apply config changes
icloud-image-labeler daemon stop # Uninstall Launch Agent
The daemon runs as com.image-labeler under ~/Library/LaunchAgents/. It auto-restarts on crash and starts on login. Logs go to ~/.image-labeler/daemon.log.
Metrics
Processing metrics are stored in SQLite at ~/.image-labeler/metrics.db.
icloud-image-labeler metrics serve # Open Datasette UI at http://localhost:8001
icloud-image-labeler metrics serve --port 9000 # Custom port
icloud-image-labeler metrics path # Print database path
Per-item metrics: export/LLM/write durations, image dimensions, LLM retry count, keyword count, OCR presence, error details, and model used.
Per-run metrics: items found/processed/failed, wall-clock duration, model, thread count, and average LLM latency.
Architecture
labeler/
cli.py — argparse CLI (init, run, daemon, config, metrics subcommands)
init.py — interactive first-run setup wizard
config.py — ~/.image-labeler/config.json management
discovery.py — osxphotos query for unprocessed media
exporter.py — photo export (HEIC/iCloud fallbacks), video frame extraction
llm.py — OpenAI-compatible client, JSON parsing, retry logic
processor.py — batch orchestration, parallel photos + sequential videos
writer.py — PhotoScript metadata writes (thread-safe)
metrics.py — SQLite metrics recording
daemon.py — macOS Launch Agent lifecycle
shutdown.py — graceful shutdown (SIGINT/SIGTERM handling)
checks.py — pre-flight dependency checks (ffmpeg, Photos.app)
License
MIT
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 icloud_image_labeler-0.1.2.tar.gz.
File metadata
- Download URL: icloud_image_labeler-0.1.2.tar.gz
- Upload date:
- Size: 21.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f8fb62e781688423e98cd67d8b8abf791198cd2c59508fc4a42e94796d3109c5
|
|
| MD5 |
689cc022ca5d106add4e3c9d55641b38
|
|
| BLAKE2b-256 |
4b6f4f9b618646c9bfb83bfedd930b9fae294318647efaf428d688d2b12dee46
|
File details
Details for the file icloud_image_labeler-0.1.2-py3-none-any.whl.
File metadata
- Download URL: icloud_image_labeler-0.1.2-py3-none-any.whl
- Upload date:
- Size: 25.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46fade35491c63551c3ec2caedd55fb67bea91d4b5d81069b6f0c61c5415c4e4
|
|
| MD5 |
267651a738fded3d63dc73419acd9741
|
|
| BLAKE2b-256 |
e9c79c9ab0b2a629939f4030d4c0f47f7528148f24eb4a883d2fb856b6fb073e
|