summaryrefslogtreecommitdiffstats
path: root/scripts/faces.py
diff options
context:
space:
mode:
authorKen D'Ambrosio <ken@jots.org>2026-06-08 17:09:51 +0000
committerKen D'Ambrosio <ken@jots.org>2026-06-08 17:09:51 +0000
commitda28a20f091372375822f9dde4486ecade859e7e (patch)
tree80d02f26c1b9d52f1a09e36f5d8946b1e3fedf6a /scripts/faces.py
parent4ba9f6451f5ab1e5ae95c0871d6fa594f49372cc (diff)
Add opt-in facial recognition: detection and embedding storage
- scripts/faces.py: Python helper using face_recognition (dlib/HOG) to detect faces and return 128-D encodings as JSON; called by update.rb - scripts/update.rb: enrich_faces() stores face boxes and encodings in album.json per image (null = not yet processed, [] = processed/none found); skips files already processed; gated on faces.enabled in config.yml - Reads CONFIG_PATH (same env var as app.rb) to check faces.enabled flag - Feature is off by default; enabled in this install via config.yml - README.md, DESIGN.md: document installation, opt-in config, data model, and planned clustering/people-management pipeline People management UI and clustering script are the next milestone. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'scripts/faces.py')
-rw-r--r--scripts/faces.py51
1 files changed, 51 insertions, 0 deletions
diff --git a/scripts/faces.py b/scripts/faces.py
new file mode 100644
index 0000000..d072376
--- /dev/null
+++ b/scripts/faces.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+"""
+Detect faces in an image and return their bounding boxes and 128-D encodings.
+
+Usage: python3 faces.py <image_path>
+
+Stdout: JSON array — one object per face:
+ [{"box": [top, right, bottom, left], "encoding": [128 floats]}, ...]
+
+Returns "[]" when no faces are found or the image cannot be opened.
+Errors are written to stderr; stdout is always valid JSON.
+"""
+import sys
+import json
+
+
+def main():
+ if len(sys.argv) < 2:
+ print("[]")
+ return
+
+ path = sys.argv[1]
+ try:
+ import face_recognition
+ except ImportError as e:
+ print(f"face_recognition not available: {e}", file=sys.stderr)
+ print("[]")
+ return
+
+ try:
+ img = face_recognition.load_image_file(path)
+ except Exception as e:
+ print(f"Could not load {path}: {e}", file=sys.stderr)
+ print("[]")
+ return
+
+ try:
+ locations = face_recognition.face_locations(img, model="hog")
+ encodings = face_recognition.face_encodings(img, locations)
+ result = [
+ {"box": list(loc), "encoding": enc.tolist()}
+ for loc, enc in zip(locations, encodings)
+ ]
+ print(json.dumps(result))
+ except Exception as e:
+ print(f"Detection error for {path}: {e}", file=sys.stderr)
+ print("[]")
+
+
+if __name__ == "__main__":
+ main()