# retoor """Validate gallery source files against the catalogue contract. Usage: python tools/gallery/check.py [media_dir] Checks every flyer-*.jpg and meme-*.jpg in the media directory (default: the repo static/media tree) plus every entry of molodetz.gallery.GALLERY: - name is lowercase, starts with flyer- or meme-, ends in .jpg - flyers are 1080x1350, memes are 1080x1080 (1280x720 legacy memes warn) - every source file is catalogued or retired, every catalogue entry has a file (repo tree only; scratch dirs get warnings) - catalogue captions are non-empty English without em or en dashes - no two catalogued works are perceptual near-duplicates (sync would skip one) Exit code 0 when clean, 1 when problems are found. Warnings do not fail. """ import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent.parent sys.path.insert(0, str(REPO_ROOT)) from PIL import Image # noqa: E402 import imagehash # noqa: E402 from molodetz.gallery import DUPLICATE_DISTANCE, GALLERY, RETIRED_GALLERY # noqa: E402 DIMENSIONS = {"flyer": (1080, 1350), "meme": (1080, 1080)} LEGACY_MEME = (1280, 720) def check(media_dir, strict=True): errors, warnings = [], [] catalogued = {filename: (kind, caption) for filename, kind, caption in GALLERY} retired = set(RETIRED_GALLERY) files = sorted(path for path in media_dir.iterdir() if path.suffix.lower() == ".jpg" and path.stem.split("-")[0].lower() in ("flyer", "meme")) seen = [] for path in files: name = path.name if name != name.lower(): errors.append(f"{name}: name must be lowercase") kind = "flyer" if name.lower().startswith("flyer-") else "meme" if name not in catalogued and name not in retired: if strict: errors.append(f"{name}: present on disk but missing from GALLERY") continue warnings.append(f"{name}: not catalogued yet, add a GALLERY entry on handoff") caption = "" elif name in retired: continue else: expected_kind, caption = catalogued[name] if expected_kind != kind: errors.append(f"{name}: catalogued as {expected_kind}, prefix says {kind}") if strict and (not caption or not caption.strip()): errors.append(f"{name}: catalogue caption is empty") elif "\u2014" in caption or "\u2013" in caption: errors.append(f"{name}: caption contains an em or en dash") with Image.open(path) as image: size = image.size digest = imagehash.phash(image) expected = DIMENSIONS[kind] if size != expected: if kind == "meme" and size == LEGACY_MEME: warnings.append(f"{name}: legacy 1280x720 size, new memes must be 1080x1080") else: errors.append(f"{name}: size {size[0]}x{size[1]}, expected {expected[0]}x{expected[1]}") for other_name, other_digest in seen: if digest - other_digest <= DUPLICATE_DISTANCE: errors.append(f"{name}: near-duplicate of {other_name}, sync would skip it") seen.append((name, digest)) on_disk = {path.name for path in files} if strict: for filename in catalogued: if filename not in on_disk: errors.append(f"{filename}: catalogued in GALLERY but missing from {media_dir}") return errors, warnings def main(argv): default_dir = REPO_ROOT / "molodetz" / "static" / "media" media_dir = Path(argv[1]) if len(argv) > 1 else default_dir if not media_dir.is_dir(): print(f"missing directory: {media_dir}") return 1 strict = media_dir.resolve() == default_dir.resolve() errors, warnings = check(media_dir, strict=strict) for line in warnings: print(f"warn: {line}") for line in errors: print(f"error: {line}") if errors: print(f"{len(errors)} error(s), {len(warnings)} warning(s)") return 1 print(f"clean: {media_dir} ({len(warnings)} warning(s))") return 0 if __name__ == "__main__": raise SystemExit(main(sys.argv))