feat: audit media catalog integrity
This commit is contained in:
@@ -120,6 +120,40 @@ def review_asset(
|
||||
return item
|
||||
|
||||
|
||||
def audit_media_catalog(root: Path) -> dict:
|
||||
manifest = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
|
||||
statuses: dict[str, int] = {}
|
||||
issues: list[str] = []
|
||||
referenced: set[str] = set()
|
||||
for item in manifest.get("assets", []):
|
||||
status = str(item.get("status", "unknown"))
|
||||
statuses[status] = statuses.get(status, 0) + 1
|
||||
local_path = item.get("local_path")
|
||||
if status in {"stored", "approved"}:
|
||||
if not isinstance(local_path, str):
|
||||
issues.append(f"{item['asset_url']}: {status} asset has no local_path")
|
||||
continue
|
||||
referenced.add(local_path)
|
||||
target = root / local_path
|
||||
if not target.is_file():
|
||||
issues.append(f"{item['asset_url']}: local file is missing")
|
||||
continue
|
||||
body = target.read_bytes()
|
||||
if hashlib.sha256(body).hexdigest() != item.get("sha256"):
|
||||
issues.append(f"{item['asset_url']}: SHA-256 mismatch")
|
||||
try:
|
||||
width, height, mime = inspect_image(body)
|
||||
if (width, height, mime) != (item.get("width"), item.get("height"), item.get("content_type")):
|
||||
issues.append(f"{item['asset_url']}: image metadata mismatch")
|
||||
except ValueError as exc:
|
||||
issues.append(f"{item['asset_url']}: {exc}")
|
||||
if status == "approved" and (not item.get("entity_key") or item.get("entity_type") not in {"fish", "waterbody", "tackle", "reference"}):
|
||||
issues.append(f"{item['asset_url']}: approved asset has no valid canonical mapping")
|
||||
files_root = root / "files"
|
||||
orphaned = sorted(str(path.relative_to(root)) for path in files_root.rglob("*") if path.is_file() and str(path.relative_to(root)) not in referenced) if files_root.exists() else []
|
||||
return {"total": sum(statuses.values()), "statuses": statuses, "issues": issues, "orphaned_files": orphaned}
|
||||
|
||||
|
||||
def inspect_image(body: bytes) -> tuple[int, int, str]:
|
||||
try:
|
||||
with Image.open(io.BytesIO(body)) as image:
|
||||
|
||||
Reference in New Issue
Block a user