refactor: collapse Config proxy, migrate tracker to SQLite, split reconcile module

- Config: remove _ConfigAccessor and ConfigManager; use __getattr__ for lazy
  loading on single _Config class; re-register self as _instance in __getattr__
  so reset() always clears the correct object (item 1)
- upload_tracker: replace hand-rolled JSON store with sqlite3; auto-migrates
  existing JSON on first run; remove dead record_frigate_file function;
  connection re-opens when CACHE_DIR changes for test isolation (items 2, 8)
- diversity: move ThreadPoolExecutor import to module level; inject optional
  fetch_fn parameter for testability (items 3, 6)
- pyproject: consolidate 4 variant files into extras (gpu/rocm/intel/cpu);
  update Dockerfile to use --extra flag; delete variant pyproject/lock files;
  uv.lock needs regen with `uv lock` after this change (item 4)
- jobs: extract _build_job helper to separate business logic from terminal I/O;
  auto_configure delegates dedup/selection to _build_job (item 5)
- logging: convert f-string log calls to % interpolation throughout all winnow/
  modules (item 7)
- reconcile: new module with reconcile_frigate_mappings and
  enrich_asset_with_face_data extracted from executor.py (item 9)
- scheduler: print next scheduled run time after startup and after each run;
  fix f-string logger.error call (item 10)
This commit is contained in:
2026-06-14 19:59:17 +00:00
parent ad1fbd4c2a
commit e2a1924fb0
22 changed files with 754 additions and 6613 deletions
+5 -5
View File
@@ -37,7 +37,7 @@ def align_face(img: Image.Image, landmarks: list[list[float]] | np.ndarray) -> I
img_np = np.asarray(img)
lm = np.array(landmarks, dtype=np.float32)
if lm.shape != (5, 2):
logger.debug(f"Invalid landmark shape: {lm.shape}, expected (5, 2)")
logger.debug("Invalid landmark shape: %s, expected (5, 2)", lm.shape)
return None
aligned = norm_crop(img_np, lm)
return Image.fromarray(aligned)
@@ -45,7 +45,7 @@ def align_face(img: Image.Image, landmarks: list[list[float]] | np.ndarray) -> I
logger.debug("InsightFace not available for face alignment")
return None
except Exception as e:
logger.debug(f"Face alignment failed: {e}")
logger.debug("Face alignment failed: %s", e)
return None
@@ -79,7 +79,7 @@ def process_face_mode(
break
if not face_info:
logger.debug(f"No face info for {person.get('name')} in asset {asset.get('id')}")
logger.debug("No face info for %s in asset %s", person.get("name"), asset.get("id"))
return None
img_w, img_h = img.size
@@ -95,7 +95,7 @@ def process_face_mode(
face_w, face_h = x2 - x1, y2 - y1
if face_w < min_width or face_h < min_width:
logger.debug(f"Face too small ({face_w:.1f}x{face_h:.1f})")
logger.debug("Face too small (%.1fx%.1f)", face_w, face_h)
return None
# Re-detect face with InsightFace for landmark-based alignment.
@@ -131,7 +131,7 @@ def process_face_mode(
_save_jpeg(aligned, os.path.join(output_dir, f"{count}.jpg"))
return aligned.size
except Exception as e:
logger.debug(f"InsightFace re-detection failed for {asset.get('id')}: {e}")
logger.debug("InsightFace re-detection failed for %s: %s", asset.get("id"), e)
# Landmark alignment from Immich metadata (Immich does not currently
# expose landmarks, so this path is a future-proofing fallback)