refactor: merge dev → main — post-0.4.0 cleanup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+8
-3
@@ -395,6 +395,7 @@ def upload_to_frigate(jobs: list[dict]) -> None:
|
||||
actually_uploaded: list[tuple[str, str | None]] = []
|
||||
failed_deletes: set[str] = set()
|
||||
min_quality_score_for_slot: float | None = None
|
||||
person_has_fscores: bool = has_frigate_scores(name)
|
||||
|
||||
for fname in person_files:
|
||||
fpath = os.path.join(person_dir, fname)
|
||||
@@ -427,9 +428,9 @@ def upload_to_frigate(jobs: list[dict]) -> None:
|
||||
# handles this conservatively by skipping that candidate until the next run.
|
||||
pre_fscore: float | None = None
|
||||
if Config.ENABLE_FRIGATE_SCORES and pre_run_count > 0:
|
||||
if not at_cap or has_frigate_scores(name):
|
||||
if not at_cap or person_has_fscores:
|
||||
_result = recognize_face(fpath)
|
||||
if _result is not None and _result[0] == name:
|
||||
if _result is not None and (_result[0] or "").casefold() == name.casefold():
|
||||
pre_fscore = _result[1]
|
||||
|
||||
# Ceiling check: skip if the existing training set already covers this
|
||||
@@ -450,7 +451,7 @@ def upload_to_frigate(jobs: list[dict]) -> None:
|
||||
progress.advance(upload_task)
|
||||
continue
|
||||
|
||||
using_fscore = has_frigate_scores(name) and Config.ENABLE_FRIGATE_SCORES
|
||||
using_fscore = person_has_fscores and Config.ENABLE_FRIGATE_SCORES
|
||||
if using_fscore:
|
||||
candidate_score = pre_fscore
|
||||
if candidate_score is None:
|
||||
@@ -476,6 +477,7 @@ def upload_to_frigate(jobs: list[dict]) -> None:
|
||||
)
|
||||
if delete_frigate_person_files(name, [target_frigate_file]):
|
||||
remove_frigate_file(name, target_frigate_file)
|
||||
person_has_fscores = has_frigate_scores(name)
|
||||
effective_count -= 1
|
||||
min_quality_score_for_slot = None # clear any blur-mode slot floor — Frigate uses a different score metric
|
||||
else:
|
||||
@@ -507,6 +509,7 @@ def upload_to_frigate(jobs: list[dict]) -> None:
|
||||
)
|
||||
if delete_frigate_person_files(name, [target_frigate_file]):
|
||||
remove_frigate_file(name, target_frigate_file)
|
||||
person_has_fscores = has_frigate_scores(name)
|
||||
effective_count -= 1
|
||||
min_quality_score_for_slot = score_map.get(fname)
|
||||
else:
|
||||
@@ -538,6 +541,8 @@ def upload_to_frigate(jobs: list[dict]) -> None:
|
||||
crop_dims=dims_map.get(fname),
|
||||
frigate_score=pre_fscore,
|
||||
)
|
||||
if pre_fscore is not None:
|
||||
person_has_fscores = True
|
||||
actually_uploaded.append((fname, asset_id))
|
||||
|
||||
break
|
||||
|
||||
+14
-18
@@ -22,24 +22,6 @@ def _get_faces_data() -> dict | None:
|
||||
return None
|
||||
|
||||
|
||||
def get_frigate_face_counts() -> dict[str, int] | None:
|
||||
"""Return {person_name: training_image_count} from Frigate's train directory.
|
||||
|
||||
Returns None if FRIGATE_URL is not set or the API is unreachable, so callers
|
||||
can distinguish "API unavailable" from "person has 0 images."
|
||||
"""
|
||||
data = _get_faces_data()
|
||||
if data is None:
|
||||
return None
|
||||
# Response: {person_name: [file, ...], "train": [...], ...}
|
||||
# "train" is a flat pending list, not a person — skip it.
|
||||
return {
|
||||
name: len(files)
|
||||
for name, files in data.items()
|
||||
if name != "train" and isinstance(files, list)
|
||||
}
|
||||
|
||||
|
||||
def get_all_frigate_person_files() -> dict[str, list[str]] | None:
|
||||
"""Return {person_name: [filename, ...]} for every person in Frigate.
|
||||
|
||||
@@ -49,6 +31,8 @@ def get_all_frigate_person_files() -> dict[str, list[str]] | None:
|
||||
data = _get_faces_data()
|
||||
if data is None:
|
||||
return None
|
||||
# Response: {person_name: [file, ...], "train": [...], ...}
|
||||
# "train" is a flat pending list, not a person — skip it.
|
||||
return {
|
||||
name: files
|
||||
for name, files in data.items()
|
||||
@@ -56,6 +40,18 @@ def get_all_frigate_person_files() -> dict[str, list[str]] | None:
|
||||
}
|
||||
|
||||
|
||||
def get_frigate_face_counts() -> dict[str, int] | None:
|
||||
"""Return {person_name: training_image_count} from Frigate's train directory.
|
||||
|
||||
Returns None if FRIGATE_URL is not set or the API is unreachable, so callers
|
||||
can distinguish "API unavailable" from "person has 0 images."
|
||||
"""
|
||||
all_files = get_all_frigate_person_files()
|
||||
if all_files is None:
|
||||
return None
|
||||
return {name: len(files) for name, files in all_files.items()}
|
||||
|
||||
|
||||
def get_frigate_person_files(person_name: str) -> list[str] | None:
|
||||
"""Return the list of training filenames for a person in Frigate.
|
||||
|
||||
|
||||
+21
-31
@@ -167,7 +167,9 @@ def remove_frigate_file(person_name: str, frigate_filename: str) -> None:
|
||||
data = _load(UPLOAD_TRACKER_FILE)
|
||||
by_person = data.get("by_person", {})
|
||||
entry = _migrate_entry(by_person.get(person_name, {}))
|
||||
entry["frigate_files"].pop(frigate_filename, None)
|
||||
asset_id = entry["frigate_files"].pop(frigate_filename, None)
|
||||
if asset_id:
|
||||
entry["frigate_scores"].pop(asset_id, None)
|
||||
by_person[person_name] = entry
|
||||
_save(UPLOAD_TRACKER_FILE, data)
|
||||
logger.debug(f"Removed Frigate file mapping {frigate_filename} ({person_name})")
|
||||
@@ -204,6 +206,22 @@ def has_frigate_scores(person_name: str) -> bool:
|
||||
return any(asset_id in frigate_scores for asset_id in frigate_files.values())
|
||||
|
||||
|
||||
def _pick_mapped_file(
|
||||
person_name: str, score_key: str, *, highest: bool, exclude: set[str] | None = None
|
||||
) -> tuple[str, str, float] | None:
|
||||
data = _load(UPLOAD_TRACKER_FILE)
|
||||
entry = _migrate_entry(data.get("by_person", {}).get(person_name, {}))
|
||||
scores = entry.get(score_key, {})
|
||||
candidates = [
|
||||
(ff, asset_id, scores[asset_id])
|
||||
for ff, asset_id in entry.get("frigate_files", {}).items()
|
||||
if (exclude is None or ff not in exclude) and asset_id in scores
|
||||
]
|
||||
if not candidates:
|
||||
return None
|
||||
return max(candidates, key=lambda x: x[2]) if highest else min(candidates, key=lambda x: x[2])
|
||||
|
||||
|
||||
def get_lowest_quality_mapped_file(
|
||||
person_name: str, exclude: set[str] | None = None
|
||||
) -> tuple[str, str, float] | None:
|
||||
@@ -213,21 +231,7 @@ def get_lowest_quality_mapped_file(
|
||||
Used for quality replacement when no Frigate scores are available.
|
||||
Pass `exclude` to skip files that failed to delete this run.
|
||||
"""
|
||||
data = _load(UPLOAD_TRACKER_FILE)
|
||||
entry = _migrate_entry(data.get("by_person", {}).get(person_name, {}))
|
||||
frigate_files = entry.get("frigate_files", {})
|
||||
blur_scores = entry.get("scores", {})
|
||||
|
||||
candidates = [
|
||||
(ff, asset_id, blur_scores[asset_id])
|
||||
for ff, asset_id in frigate_files.items()
|
||||
if (exclude is None or ff not in exclude)
|
||||
and asset_id in blur_scores
|
||||
]
|
||||
|
||||
if not candidates:
|
||||
return None
|
||||
return min(candidates, key=lambda x: x[2])
|
||||
return _pick_mapped_file(person_name, "scores", highest=False, exclude=exclude)
|
||||
|
||||
|
||||
def get_most_redundant_mapped_file(
|
||||
@@ -240,21 +244,7 @@ def get_most_redundant_mapped_file(
|
||||
= the most redundant file and therefore the best replacement target.
|
||||
Pass `exclude` to skip files that failed to delete this run.
|
||||
"""
|
||||
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", {})
|
||||
|
||||
candidates = [
|
||||
(ff, asset_id, frigate_scores[asset_id])
|
||||
for ff, asset_id in frigate_files.items()
|
||||
if (exclude is None or ff not in exclude)
|
||||
and asset_id in frigate_scores
|
||||
]
|
||||
|
||||
if not candidates:
|
||||
return None
|
||||
return max(candidates, key=lambda x: x[2])
|
||||
return _pick_mapped_file(person_name, "frigate_scores", highest=True, exclude=exclude)
|
||||
|
||||
|
||||
def get_frigate_filename_for_asset(person_name: str, asset_id: str) -> str | None:
|
||||
|
||||
Reference in New Issue
Block a user