fix: stale frigate mapping reconciliation, recognize opt-out, cold start notice
- At upload start, diff tracker vs live Frigate file list and remove any mappings for files no longer present; corrects effective_count so manually deleted files don't permanently consume quota slots - Add ENABLE_FRIGATE_SCORES config (default true); when false, skips all recognize_face calls and falls back to blur scores for quality replacement - Print a dim notice when FRIGATE_SCORE_THRESHOLD is set but pre_run_count is zero, so users know the gate is deferred to the next run Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,7 @@ STRATEGY=auto
|
|||||||
# BLUR_THRESHOLD=120.0 # Laplacian blur threshold; lower = accept more blur (default: 120.0)
|
# BLUR_THRESHOLD=120.0 # Laplacian blur threshold; lower = accept more blur (default: 120.0)
|
||||||
# MAX_AUTO_IMAGES=80 # Hard cap on auto-diversity selection (default: 80)
|
# MAX_AUTO_IMAGES=80 # Hard cap on auto-diversity selection (default: 80)
|
||||||
# FRIGATE_SCORE_THRESHOLD=0.0 # Quality gate: remove images scoring below this after upload (0 = disabled; requires at least one prior run)
|
# FRIGATE_SCORE_THRESHOLD=0.0 # Quality gate: remove images scoring below this after upload (0 = disabled; requires at least one prior run)
|
||||||
|
# ENABLE_FRIGATE_SCORES=true # Call Frigate's recognize endpoint after each upload to store quality scores (default: true; adds ~200ms per upload)
|
||||||
|
|
||||||
# ── Caching & Models ──────────────────────────────────────────────────────────
|
# ── Caching & Models ──────────────────────────────────────────────────────────
|
||||||
# FORCE_CPU=true # Disable GPU, fall back to CPU
|
# FORCE_CPU=true # Disable GPU, fall back to CPU
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class _Config:
|
|||||||
MAX_AUTO_IMAGES: int = 80
|
MAX_AUTO_IMAGES: int = 80
|
||||||
QUALITY_REPLACEMENT: bool = True
|
QUALITY_REPLACEMENT: bool = True
|
||||||
FRIGATE_SCORE_THRESHOLD: float = 0.0
|
FRIGATE_SCORE_THRESHOLD: float = 0.0
|
||||||
|
ENABLE_FRIGATE_SCORES: bool = True
|
||||||
|
|
||||||
# People filtering
|
# People filtering
|
||||||
MIN_FACE_COUNT: int = 0
|
MIN_FACE_COUNT: int = 0
|
||||||
@@ -64,6 +65,7 @@ class _Config:
|
|||||||
self.MAX_AUTO_IMAGES = int(os.getenv("MAX_AUTO_IMAGES", "80"))
|
self.MAX_AUTO_IMAGES = int(os.getenv("MAX_AUTO_IMAGES", "80"))
|
||||||
self.QUALITY_REPLACEMENT = os.getenv("QUALITY_REPLACEMENT", "true").lower() in ("true", "1", "yes")
|
self.QUALITY_REPLACEMENT = os.getenv("QUALITY_REPLACEMENT", "true").lower() in ("true", "1", "yes")
|
||||||
self.FRIGATE_SCORE_THRESHOLD = float(os.getenv("FRIGATE_SCORE_THRESHOLD", "0.0"))
|
self.FRIGATE_SCORE_THRESHOLD = float(os.getenv("FRIGATE_SCORE_THRESHOLD", "0.0"))
|
||||||
|
self.ENABLE_FRIGATE_SCORES = os.getenv("ENABLE_FRIGATE_SCORES", "true").lower() in ("true", "1", "yes")
|
||||||
self.FACE_MARGIN = float(os.getenv("FACE_MARGIN", "0.15"))
|
self.FACE_MARGIN = float(os.getenv("FACE_MARGIN", "0.15"))
|
||||||
self.USE_FULL_RESOLUTION = os.getenv("USE_FULL_RESOLUTION", "true").lower() in ("true", "1", "yes")
|
self.USE_FULL_RESOLUTION = os.getenv("USE_FULL_RESOLUTION", "true").lower() in ("true", "1", "yes")
|
||||||
self.ENABLE_FACE_ALIGNMENT = os.getenv("ENABLE_FACE_ALIGNMENT", "true").lower() in ("true", "1", "yes")
|
self.ENABLE_FACE_ALIGNMENT = os.getenv("ENABLE_FACE_ALIGNMENT", "true").lower() in ("true", "1", "yes")
|
||||||
|
|||||||
+17
-2
@@ -357,6 +357,17 @@ def upload_to_frigate(jobs: list[dict]) -> None:
|
|||||||
known_frigate_files_at_start: set[str] = get_tracked_frigate_filenames(name)
|
known_frigate_files_at_start: set[str] = get_tracked_frigate_filenames(name)
|
||||||
else:
|
else:
|
||||||
known_frigate_files_at_start: set[str] = set(_snapshot)
|
known_frigate_files_at_start: set[str] = set(_snapshot)
|
||||||
|
# Remove tracker mappings for files that no longer exist in Frigate
|
||||||
|
# (manually deleted, or cleaned up outside winnow). This corrects the
|
||||||
|
# effective_count so those slots are available for new uploads.
|
||||||
|
stale = get_tracked_frigate_filenames(name) - known_frigate_files_at_start
|
||||||
|
for stale_fn in stale:
|
||||||
|
remove_frigate_file(name, stale_fn)
|
||||||
|
if stale:
|
||||||
|
progress.console.print(
|
||||||
|
f" [dim]{name}: cleared {len(stale)} stale mapping(s)"
|
||||||
|
" (file(s) no longer in Frigate)[/dim]"
|
||||||
|
)
|
||||||
effective_count = get_tracked_frigate_file_count(name)
|
effective_count = get_tracked_frigate_file_count(name)
|
||||||
pre_run_count = effective_count
|
pre_run_count = effective_count
|
||||||
quality_replacement = job.get("config", {}).get("quality_replacement", False)
|
quality_replacement = job.get("config", {}).get("quality_replacement", False)
|
||||||
@@ -367,6 +378,10 @@ def upload_to_frigate(jobs: list[dict]) -> None:
|
|||||||
effective_threshold = max(Config.FRIGATE_SCORE_THRESHOLD, _dynamic or 0.0)
|
effective_threshold = max(Config.FRIGATE_SCORE_THRESHOLD, _dynamic or 0.0)
|
||||||
if _dynamic is not None and _dynamic > Config.FRIGATE_SCORE_THRESHOLD:
|
if _dynamic is not None and _dynamic > Config.FRIGATE_SCORE_THRESHOLD:
|
||||||
logger.debug(f"{name}: dynamic Frigate score threshold {_dynamic:.3f}")
|
logger.debug(f"{name}: dynamic Frigate score threshold {_dynamic:.3f}")
|
||||||
|
if pre_run_count == 0:
|
||||||
|
progress.console.print(
|
||||||
|
f" [dim]{name}: first run — quality gate will apply from the next run[/dim]"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
effective_threshold = 0.0
|
effective_threshold = 0.0
|
||||||
actually_uploaded: list[tuple[str, str | None]] = []
|
actually_uploaded: list[tuple[str, str | None]] = []
|
||||||
@@ -397,7 +412,7 @@ def upload_to_frigate(jobs: list[dict]) -> None:
|
|||||||
progress.console.print(f" [dim]⏭ {fname}: at cap, quality replacement disabled[/dim]")
|
progress.console.print(f" [dim]⏭ {fname}: at cap, quality replacement disabled[/dim]")
|
||||||
progress.advance(upload_task)
|
progress.advance(upload_task)
|
||||||
continue
|
continue
|
||||||
using_fscore = has_frigate_scores(name)
|
using_fscore = has_frigate_scores(name) and Config.ENABLE_FRIGATE_SCORES
|
||||||
if using_fscore:
|
if using_fscore:
|
||||||
candidate_score = recognize_face(fpath)
|
candidate_score = recognize_face(fpath)
|
||||||
if candidate_score is None:
|
if candidate_score is None:
|
||||||
@@ -458,7 +473,7 @@ def upload_to_frigate(jobs: list[dict]) -> None:
|
|||||||
|
|
||||||
asset_id = asset_map.get(fname)
|
asset_id = asset_map.get(fname)
|
||||||
if asset_id:
|
if asset_id:
|
||||||
post_fscore = recognize_face(fpath)
|
post_fscore = recognize_face(fpath) if Config.ENABLE_FRIGATE_SCORES else None
|
||||||
mark_uploaded(
|
mark_uploaded(
|
||||||
asset_id,
|
asset_id,
|
||||||
person_name=name,
|
person_name=name,
|
||||||
|
|||||||
Reference in New Issue
Block a user