diff --git a/winnow/executor.py b/winnow/executor.py index c8461b5..ab02eef 100644 --- a/winnow/executor.py +++ b/winnow/executor.py @@ -600,7 +600,10 @@ def upload_to_frigate(jobs: list[dict]) -> None: ) finally: - flush_batch(UPLOAD_TRACKER_FILE) + try: + flush_batch(UPLOAD_TRACKER_FILE) + except Exception as _flush_exc: + logger.warning("flush_batch failed during cleanup — batch will be recovered on next begin_batch: %s", _flush_exc) # Batch-map Frigate filenames to asset IDs now that all uploads are done. if actually_uploaded and not _skip_reconcile: diff --git a/winnow/quality.py b/winnow/quality.py index bd78d7a..1becded 100644 --- a/winnow/quality.py +++ b/winnow/quality.py @@ -14,6 +14,11 @@ from PIL import Image logger = logging.getLogger(__name__) +def _laplacian_var(img_np: np.ndarray) -> float: + gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY) if img_np.ndim == 3 else img_np + return float(cv2.Laplacian(gray, cv2.CV_64F).var()) + + @dataclass class QualityResult: """Result of quality assessment on a face/image crop.""" @@ -32,8 +37,7 @@ def check_blur(img_np: np.ndarray, threshold: float = 100.0) -> tuple[bool, str] Lower variance = blurrier image. ArcFace needs clear facial features. """ - gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY) if img_np.ndim == 3 else img_np - variance = cv2.Laplacian(gray, cv2.CV_64F).var() + variance = _laplacian_var(img_np) if variance < threshold: return False, f"Blurry (laplacian={variance:.1f}, threshold={threshold})" return True, "" @@ -115,8 +119,7 @@ def assess_quality( reasons = [] # Compute laplacian variance once (used by check_blur and stored as blur_score) - gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY) if img_np.ndim == 3 else img_np - blur_score = float(cv2.Laplacian(gray, cv2.CV_64F).var()) + blur_score = _laplacian_var(img_np) checks = [ ( @@ -154,8 +157,7 @@ def blur_score_from_image(img: Image.Image, max_dim: int = 1440) -> float | None if score_img.width > max_dim or score_img.height > max_dim: score_img = score_img.copy() score_img.thumbnail((max_dim, max_dim), Image.LANCZOS) - gray = cv2.cvtColor(np.array(score_img), cv2.COLOR_RGB2GRAY) - return float(cv2.Laplacian(gray, cv2.CV_64F).var()) + return _laplacian_var(np.array(score_img)) except Exception as exc: logger.debug("blur_score_from_image failed: %s", exc) return None diff --git a/winnow/upload_tracker.py b/winnow/upload_tracker.py index e29f440..69d741a 100644 --- a/winnow/upload_tracker.py +++ b/winnow/upload_tracker.py @@ -281,9 +281,11 @@ def get_tracked_frigate_filenames(person_name: str) -> set[str]: def has_frigate_scores(person_name: str) -> bool: """Return True if any mapped file for this person has a stored Frigate recognition score.""" data = _load(UPLOAD_TRACKER_FILE) - entry = _migrate_entry(data.get("by_person", {}).get(person_name, {})) - frigate_files = entry.get("frigate_files", {}) - frigate_scores = entry.get("frigate_scores", {}) + raw = data.get("by_person", {}).get(person_name) + if not raw or isinstance(raw, list): + return False + frigate_files = raw.get("frigate_files", {}) + frigate_scores = raw.get("frigate_scores", {}) return any(asset_id in frigate_scores for asset_id in frigate_files.values())