v0.6.9: stop serving match-template crops as hero portraits.
Export and OSS upload require wide Heroes-page cards only; bump cache stamp to wide2 so clients drop polluted squares. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -84,6 +84,51 @@ def _build_staging() -> tuple[Path, dict[str, int]]:
|
||||
return staging, counts
|
||||
|
||||
|
||||
def _png_size(path: Path) -> tuple[int, int] | None:
|
||||
"""Return (width, height) for a PNG, or None if unreadable."""
|
||||
try:
|
||||
raw = path.read_bytes()
|
||||
except OSError:
|
||||
return None
|
||||
if len(raw) < 24 or raw[:8] != b"\x89PNG\r\n\x1a\n":
|
||||
return None
|
||||
# IHDR: length(4) + type(4) + width(4) + height(4)
|
||||
if raw[12:16] != b"IHDR":
|
||||
return None
|
||||
w = int.from_bytes(raw[16:20], "big")
|
||||
h = int.from_bytes(raw[20:24], "big")
|
||||
return w, h
|
||||
|
||||
|
||||
def _assert_wide_portraits(staging: Path) -> None:
|
||||
"""Refuse top-bar match crops (square ~96×96) before any OSS put.
|
||||
|
||||
Official cards are usually 256×144; Steam occasionally ships half-res
|
||||
128×72 landscape (still fine for the grid). Match templates are square.
|
||||
"""
|
||||
portrait_dir = staging / "portrait"
|
||||
if not portrait_dir.is_dir():
|
||||
raise SystemExit("staging missing portrait/ — run fetch_hero_portraits.py")
|
||||
bad: list[str] = []
|
||||
for path in sorted(portrait_dir.glob("*.png")):
|
||||
size = _png_size(path)
|
||||
if size is None:
|
||||
bad.append(f"{path.name}: unreadable")
|
||||
continue
|
||||
w, h = size
|
||||
# Landscape Heroes cards: aspect ≈ 16:9. Match CDN faces are square.
|
||||
if h <= 0 or w / h < 1.4:
|
||||
bad.append(f"{path.name}: {w}x{h}")
|
||||
if bad:
|
||||
sample = "; ".join(bad[:6])
|
||||
more = f" (+{len(bad) - 6} more)" if len(bad) > 6 else ""
|
||||
raise SystemExit(
|
||||
f"refusing to upload {len(bad)} non-wide portrait(s) "
|
||||
f"(need landscape aspect ≥1.4, not match-template squares): "
|
||||
f"{sample}{more}. Run: python web/fetch_hero_portraits.py"
|
||||
)
|
||||
|
||||
|
||||
def _iter_files(root: Path) -> list[Path]:
|
||||
files: list[Path] = []
|
||||
for sub in ASSET_DIRS:
|
||||
@@ -97,6 +142,7 @@ def _iter_files(root: Path) -> list[Path]:
|
||||
def upload(bucket_name: str, *, force: bool = False) -> None:
|
||||
staging, counts = _build_staging()
|
||||
try:
|
||||
_assert_wide_portraits(staging)
|
||||
files = _iter_files(staging)
|
||||
total_bytes = sum(f.stat().st_size for f in files)
|
||||
print(f"staging {staging} ({len(files)} files, {total_bytes / 1e6:.1f} MB)")
|
||||
|
||||
Reference in New Issue
Block a user