AI Powered Photo Gallery Without the Cloud
Best Photo Picker is a local-first, open-source photo curation tool that uses AI to score photos on sharpness, lighting, faces, and composition. It runs entirely on your machine with no cloud uploads, supports smart deduplication, face recognition, and temporal diversity, and offers a web UI and native macOS app.
Uh oh!
There was an error while loading. Please reload this page.
Notifications You must be signed in to change notification settings
Fork 0
Star 1
BranchesTags
Open more actions menu
Folders and files
NameName
Last commit message
Last commit date
Latest commit
History
2 Commits
2 Commits
.githooks
.githooks
.github
.github
bpp
bpp
desktop
desktop
docs
docs
examples/plugin_example
examples/plugin_example
scripts
scripts
tests-e2e
tests-e2e
tests-js
tests-js
tests
tests
.dockerignore
.dockerignore
.eslint-globals.json
.eslint-globals.json
.gitattributes
.gitattributes
.gitignore
.gitignore
.pre-commit-config.yaml
.pre-commit-config.yaml
.prettierignore
.prettierignore
.prettierrc.json
.prettierrc.json
CHANGELOG.md
CHANGELOG.md
CODE_OF_CONDUCT.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
CONTRIBUTING.md
Dockerfile
Dockerfile
LICENSE
LICENSE
MANIFEST.in
MANIFEST.in
MODEL_POLICY.md
MODEL_POLICY.md
NOTICE.txt
NOTICE.txt
README.md
README.md
SECURITY.md
SECURITY.md
bpp-server.spec
bpp-server.spec
eslint.config.js
eslint.config.js
launch.command.template
launch.command.template
package-lock.json
package-lock.json
package.json
package.json
playwright.config.mjs
playwright.config.mjs
pyproject.toml
pyproject.toml
sample_config.yaml
sample_config.yaml
tsconfig.json
tsconfig.json
vitest.config.mjs
vitest.config.mjs
Repository files navigation
Drowning in 15,000 family photos? Apple Photos and Google Photos bury your best shots in their feed. Best Photo Picker runs entirely on your machine, scores every photo on sharpness, lighting, faces, and composition, and lets you curate the perfect 50 in minutes — no cloud, no subscription, no compromise.
It's the only photo tool that auto-scores your whole library for curation without uploading anything, then clusters near-duplicates and lets you boost specific people so the final pick favors the faces that matter — your kid, for the grandparents.
Try it in 30 seconds — no photos needed:
pip install "bppicker[web]" && bpp demo
Generates a sample library, runs the full UI locally, and quits cleanly when you close the tab. Nothing leaves your machine.
By Arkalogy — a PM-directed, AI-built product; the architectural decisions are captured in docs/adr/. Why & how it was built →
For an annotated walkthrough of the full workflow — import → adjust → pick → export — plus the side surfaces (faces, calendar, map, duplicates), see docs/quickstart-gallery.md.
📣 Found a bug or have a feature idea? Open an issue · Start a discussion · Contributing guide. Security issues: please use private vulnerability reporting, not a public issue.
Why Best Photo Picker?
Best Photo Picker Apple Photos Google Photos Lightroom digiKam
Local-first / runs offline ✅ ⚠️ iCloud-coupled ❌ cloud ⚠️ cloud sync ✅
Auto-scores photos for picking ✅ ❌ ⚠️ "Memories" ⚠️ flags only ❌
Boost picks by named person ✅ ⚠️ ⚠️ ❌ ❌
Near-duplicate deduplication ✅ ⚠️ ⚠️ ❌ ✅
Free + open source ✅ MIT ❌ ❌ ❌ $120/yr ✅ GPL
No account / no subscription ✅ ❌ ❌ ❌ ✅
Optional LAN sharing w/ device pairing ✅ ❌ ❌ ❌ ❌
If you live in the Apple/Google ecosystem and your collection fits their feeds, those tools work great. bpp exists for the case they're bad at: you have thousands of photos of a specific subject — your kid, your dog, last summer's trip — and you want to find the actual best fifty without uploading anything anywhere.
Features
Quality scoring — sharpness, exposure, face detection (YuNet + SCRFD + BlazeFace + MediaPipe + dlib), composition
Smart deduplication — perceptual hashing (dHash + aHash) and CLIP semantic similarity
Temporal diversity — per-day caps and monthly coverage for balanced selection
Face recognition — automatic clustering, per-person smart albums, merge, dismiss, reassign, drag-to-fix mis-detected bboxes
Library management — import by copying to managed library with SHA-256 dedup
Album system — manual + 19 smart album types (person, time, score, duplicates, pets, etc.)
Interactive web UI — real-time slider tuning, photo grid, lightbox, compare view, batch operations
Desktop app — native macOS app via Tauri v2 (wraps the web UI in a native window)
Soft delete — 30-day recovery, Recently Deleted album
Demo mode — try instantly with generated sample photos
Privacy — local-first, no telemetry, no analytics. The few network calls bpp makes (model downloads on first analyze, OpenStreetMap tiles when you open the Map view, update checks against GitHub Releases, optional LAN sharing, optional pip install of extra features) are all enumerated below and individually disclosable; nothing about your library is ever sent.
Quick Start
Install
Requires Python 3.11 (3.12+ is not yet supported — it's pinned to match the desktop sidecar). On macOS, Homebrew ships a newer Python by default; install 3.11 with brew install [email protected] or pyenv, or use the no-Python desktop app below.
Recommended — pipx keeps bpp in its own environment and makes updating one command:
pipx install "bppicker[web]"
Update to the latest release later:
pipx upgrade bppicker
Plain pip works too:
pip install "bppicker[web]"
Optional: face recognition
pip install "bppicker[web,faces]"
Optional: HEIC support
pip install "bppicker[heic]"
Most ML-powered features (face recognition, NudeNet, RAW import, HEIC, AI inpainting) install on demand from Settings → Advanced → ML Models in the running app, or you can pre-install everything at once with pip install "bppicker[heic,faces,raw,nudity,inpaint]".
Desktop app (macOS, Apple Silicon)
Prefer a dock icon over a terminal? Each release ships a standalone Mac app. No Python, no terminal:
Download BestPhotoPicker-macOS-arm64.dmg (always the latest release).
Double-click the .dmg and drag Best Photo Picker into Applications.
Open it from Applications or Spotlight.
The app is signed with an Apple Developer ID and notarized by Apple, so it opens on double-click — no security warning. To update, download the newer .dmg the same way; your photo library and settings live outside the app and carry over untouched.
Apple Silicon (M1/M2/M3 and later) only. On an Intel Mac, or on Windows/Linux, use the pipx install "bppicker[web]" path above.
Try the Demo
bpp demo
Generates sample photos and launches the web UI — no configuration needed.
Library Mode (Recommended)
Start the photo management server (default library: ~/Pictures/BestPhotoPicker)
bpp serve
Or specify a custom library path
bpp serve --library ~/Photos/2024
Open http://127.0.0.1:5001 in your browser. Import folders, adjust scoring weights with sliders, and export your curated selection.
CLI Mode (Batch Processing)
One-shot: analyze + select best 50 photos
bpp run --input ~/Photos/MyKid --k 50 --out ~/curated --gallery
Or step-by-step:
bpp analyze --input ~/Photos/MyKid --out ~/workdir bpp select --workdir ~/workdir --k 50 --out ~/curated --gallery
How It Works
Import → Analyze → Score → Deduplicate → Select → Export
Import: photos copied to managed library with SHA-256 dedup
Analyze: parallel feature extraction (blur, exposure, faces, composition) cached in SQLite
Score: weighted combination of sub-scores, tunable in real-time via sliders
Deduplicate: perceptual hash clustering removes burst duplicates; optional CLIP semantic dedup
Select: greedy selection with per-day caps and monthly coverage for temporal diversity
Export: copy/hardlink/symlink selected photos with optional HTML gallery
Web UI
Feature Description
Photo grid Thumbnail grid with score badges, zoom control, sort & filter
Lightbox Full-size viewer with keyboard navigation
Sliders Real-time weight tuning (blur, exposure, face, composition)
BPP Picks Sidebar sub-item picks best K photos across the full library; toolbar chip filters picks in the current album/view
Albums Manual and smart albums (by time, score, person)
Faces Auto-clustered face detection with merge, dismiss, and per-person albums
Batch ops Multi-select with Cmd/Ctrl-click, bulk include/exclude/favorite
Import Drag folders or archives into the library
Export Copy or link selected photos to an output directory
Tuning Knobs
Parameter Default Description
blur_weight 0.30 Weight for sharpness in aggregate score
exposure_weight 0.20 Weight for exposure quality
face_weight 0.35 Weight for face detection & framing
composition_weight 0.15 Weight for composition (rule of thirds)
max_long_side 1024 Downscale images to this before analysis
hash_distance_threshold 10 dHash Hamming distance for "same" image
time_window_seconds 15 Cluster burst photos within this window
max_per_day 3 Max selected photos per calendar day
min_per_month 1 Try to include at least 1 per month
Hardware
bpp runs all ML inference on CPU by default. ONNX-based models (SCRFD face detection, CLIP semantic search, YOLOv11n pet detection) can opt into hardware acceleration through the BPP_ONNX_PROVIDERS env var:
Apple Silicon (M1/M2/M3) — use the Apple Neural Engine via CoreML
BPP_ONNX_PROVIDERS="CoreMLExecutionProvider,CPUExecutionProvider" \ bpp serve --library ~/Pictures/BestPhotoPicker
NVIDIA GPU (Linux) — install onnxruntime-gpu first
pip uninstall -y onnxruntime && pip install onnxruntime-gpu BPP_ONNX_PROVIDERS="CUDAExecutionProvider,CPUExecutionProvider" \ bpp serve --library ~/Pictures/BestPhotoPicker
Windows DirectML
BPP_ONNX_PROVIDERS="DmlExecutionProvider,CPUExecutionProvider" \ bpp serve --library ~/Pictures/BestPhotoPicker
CPU is always appended as the final fallback so a missing provider in your wheel falls through cleanly with a warning rather than crashing. Ordering matters — providers are tried in the order listed.
Tested vs supported
Surface Tested in CI Supported Notes
macOS arm64 (M1/M2/M3) — CPU yes (dev box) yes Primary dev target. Tauri desktop app ships only this.
Linux x86_64 — CPU yes (ubuntu-latest) yes Default for PyPI install.
Linux arm64 — CPU no yes Docker image builds; runtime not exercised in CI.
Windows — CPU no yes Code path exists; never run end-to-end. Reports welcome via GitHub issues.
macOS arm64 — CoreMLExecutionProvider (ANE) no yes Code path exists. Expected 5-10× face inference speedup; correctness depends on the specific ONNX graph and ONNX Runtime version.
Linux x86_64 — CUDAExecutionProvider no yes Requires pip install onnxruntime-gpu (replaces base onnxruntime). Free-tier CI doesn't have a GPU runner.
Windows — DmlExecutionProvider no yes Same caveat as Windows CPU — code path exists, no end-to-end run.
"Supported" means the code path accepts the configuration and shouldn't crash; report bugs via GitHub Issues if it does. "Tested in CI" means we run the full test suite on every push for that surface. Anything between the two is on the user's risk surface — your hardware, your driver stack, your call. We'd love to expand the tested column as users report working setups; PRs welcome that add matrix entries to .github/workflows/ci.yml.
Throughput tuning
Face extraction is single-threaded per subprocess by default (_face_extract_workers=1). On a 6,000-photo library that's ~50 min on Apple Silicon (roughly 0.5 s/photo). Two config knobs let operators trade RAM for time:
settings table or YAML config
_face_extract_workers: 4 # number of parallel workers _face_extract_pool: process # "process" | "thread"
Recommended combos:
Setup workers pool Expected throughput Peak RAM
Default (safe everywhere) 1 thread 1× baseline (~50 min / 6k photos) ~700 MB
Power user, 16+ GB RAM 4 process ~3-4× (~13 min / 6k photos) ~3 GB
Measured on Apple M-series; results vary by photo resolution and system load.
The process pool is the only memory-safe parallel option — there
[truncated for AI cost control]