photo-pipeline — Dashboard & System How-To
The photo-pipeline is a Prefect-orchestrated photo ingestion system on the home server (.13). It downloads Google Takeout exports, fingerprints every image (dedup), audits quality (blurry/dark/etc), and imports approved photos to Immich.
Quick links
| Service | URL |
|---|---|
| Review dashboard (this) | http://192.168.20.13:8092 |
| Prefect UI (flow runs, logs, schedules) | http://192.168.20.13:4200 |
| Immich (photo library) | http://192.168.20.35:2283 |
| Apprise (notifications) | https://apprise.lab.audasmedia.com.au/ |
| Code repo | ssh://gitea.lab.audasmedia.com.au:2222/sam/photo-pipeline.git |
The pipeline
flowchart LR
TO[Google Takeout<br/>134GB / 14 zips] -->|drop manifest| W[photo-watch<br/>every 15 min]
W -->|download + extract| I[photo-ingest<br/>sha256 + dhash dedup]
I -->|fingerprints| Q[quality-scan<br/>PIL blurry/dark]
Q -->|verdicts| R[Review dashboard<br/>photo-filter.home.lab]
R -->|approved| M[merge_orphans<br/>EXIF date routing]
M --> B[(by_date master<br/>35K files)]
B -->|incremental import| IM[Immich<br/>.35 library]
R -->|rejected| T[(trash → purge)]
B -.->|offsite| S3[AWS S3<br/>Glacier lifecycle]
The flow explained
- Takeout — download Google Photos export archives (14 zips, ~134GB)
- photo-watch — watches incoming/, extracts, fingerprints
- photo-ingest — sha256 + dhash; content dedup (never doubles)
- quality-scan — PIL verdicts: blurry/dark flagged
- Review — approve / reject in the dashboard
- merge_orphans — approved → by_date (EXIF-date routed, rename-on-collision)
- Immich import — incremental (only new files, checksum dedup)
- Rejected → trash → purge · by_date → Borg daily + AWS S3 offsite
Interactive map: see
docs/photo-pipeline-map.htmlOpen the interactive map: https://maps.lab.audasmedia.com.au/google_cloud_and_images/docs/photo-pipeline-map.html
Deployments (Prefect, pool photo-pool)
| Flow | Purpose | Trigger |
|---|---|---|
photo-watch/watch |
Watches incoming folder, auto-chains | Every 15 min |
takeout-fetch/fetch |
Download + track + extract Takeout archives | Manual / watch |
photo-ingest/ingest |
Fingerprint + dedup a folder | Manual / watch |
photo-quality-scan/quality |
CleanVision audit → staging | Manual / watch |
immich-import/import |
Upload approved (01_keep) to Immich | Manual |
Triggering from the command line (on .13)
export PREFECT_API_URL=http://localhost:4200/api
~/photo-pipeline/.venv/bin/prefect deployment run "takeout-fetch/fetch" \
--param manifest=/mnt/data/takeout/incoming/urls.txt --param export_id=photos-2026-08
~/photo-pipeline/.venv/bin/prefect deployment run "photo-ingest/ingest" \
--param base_dir=/mnt/data/takeout/<export> --param source=takeout
Or simpler: drop the file in incoming/ and let the watch flow do it.
Key paths (on .13)
~/photo-pipeline/ code (git repo)
photo_db.py fingerprint DB module
photo_ingest.py ingest flow
takeout_fetch.py download/track/extract flow
quality_scan.py CleanVision flow
immich_import.py Immich upload flow
photo_watch.py watch-folder trigger
dashboard/ this FastAPI app
photo_pipeline.db SQLite fingerprint DB (WAL)
.immich-key Immich API key (chmod 600, gitignored)
/mnt/data/ staging root
takeout/incoming/ drop Takeout manifests/archives here
takeout/processed/ done items
01_keep/ approved, ready for Immich
02_review/ flagged, awaiting decision
03_delete/ rejected candidates (holding — never auto-deleted)
.thumbs/ generated thumbnails
Services (systemd user units on .13)
prefect-server— Docker container, port 4200prefect-worker— process worker on photo-poolphoto-dashboard— FastAPI on port 8092
systemctl --user status photo-dashboard
systemctl --user restart photo-dashboard
Safety rules
- Nothing is ever auto-deleted.
03_deleteis a holding folder; emptying it is a deliberate human act. photo_pipeline.db,.immich-key,.venv,node_modulesare gitignored — never commit secrets.- Immich API key lives in
.immich-key(chmod 600); needs scopesuser.read,asset.read,asset.upload. - SSH from .13 → .35 uses
-i ~/.ssh/id_ed25519_rsync.
Notifications (Apprise)
Self-hosted Apprise server fans out to all configured targets. The flows POST
batch summaries (counts + dashboard link). Configure targets in the Apprise UI
(https://apprise.lab.audasmedia.com.au/) — no per-machine config needed.
Description
Languages
Python
63%
HTML
35.9%
Shell
1.1%