Separate the local recognition, web publishing, and shared data paths while preserving direct script execution and existing site content. Co-authored-by: Cursor <cursoragent@cursor.com>
82 lines
2.8 KiB
Python
82 lines
2.8 KiB
Python
"""Fetch the official Dota 2 emblem icon (no wordmark) for the web site header.
|
|
|
|
Source (discovered, not guessed) from the dota2.com.cn site HTML:
|
|
<link rel="Shortcut Icon" href="//www.dota2.com.cn/favicon.ico"/>
|
|
|
|
That .ico is a multi-frame container whose largest frames are 256x256 PNGs of
|
|
the red Dota 2 map-marker emblem with a transparent background — i.e. the
|
|
brand icon alone, no "DOTA 2" text. We extract the first 256x256 PNG frame,
|
|
decode with OpenCV, and write it to assets/ui_icons/dota2_logo.png so it rides
|
|
the existing /ui-icon/ route + static-export glob pipeline.
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
import struct
|
|
|
|
import cv2
|
|
import numpy as np
|
|
|
|
from shared.http_utils import http_bytes
|
|
from shared.paths import UI_ICONS
|
|
|
|
FAVICON_URL = "https://www.dota2.com.cn/favicon.ico"
|
|
OUT = UI_ICONS / "dota2_logo.png"
|
|
TARGET_SIZE = 256
|
|
|
|
|
|
def _extract_largest_png_frame(data: bytes) -> bytes | None:
|
|
"""Return the bytes of the largest PNG-embedded frame in an ICO blob."""
|
|
if len(data) < 6 or data[:4] != b"\x00\x00\x01\x00":
|
|
return None
|
|
count = struct.unpack("<H", data[4:6])[0]
|
|
best: tuple[int, bytes] | None = None
|
|
for i in range(count):
|
|
off = 6 + 16 * i
|
|
if off + 16 > len(data):
|
|
break
|
|
w = data[off]
|
|
size = struct.unpack("<I", data[off + 8 : off + 12])[0]
|
|
img_off = struct.unpack("<I", data[off + 12 : off + 16])[0]
|
|
width = 256 if w == 0 else w
|
|
blob = data[img_off : img_off + size]
|
|
if blob[:8] != b"\x89PNG\r\n\x1a\n":
|
|
continue
|
|
if best is None or width * width > best[0]:
|
|
best = (width * width, blob)
|
|
return best[1] if best else None
|
|
|
|
|
|
def main() -> int:
|
|
if OUT.is_file() and OUT.stat().st_size >= 512:
|
|
print(f"exists: {OUT} ({OUT.stat().st_size} bytes)")
|
|
return 0
|
|
data = http_bytes(FAVICON_URL, timeout=30)
|
|
if not data or len(data) < 16:
|
|
print("empty favicon download", file=sys.stderr)
|
|
return 1
|
|
png = _extract_largest_png_frame(data)
|
|
if png is None:
|
|
print("no PNG frame in .ico", file=sys.stderr)
|
|
return 1
|
|
arr = cv2.imdecode(np.frombuffer(png, dtype=np.uint8), cv2.IMREAD_UNCHANGED)
|
|
if arr is None:
|
|
print("decode failed", file=sys.stderr)
|
|
return 1
|
|
UI_ICONS.mkdir(parents=True, exist_ok=True)
|
|
ok = cv2.imwrite(str(OUT), arr)
|
|
if not ok:
|
|
print("write failed", file=sys.stderr)
|
|
return 1
|
|
h, w = arr.shape[:2]
|
|
channels = arr.shape[2] if arr.ndim == 3 else 1
|
|
transparent = int(np.count_nonzero(arr[:, :, 3] == 0)) if channels == 4 else 0
|
|
print(f"saved {OUT} ({w}x{h}, {OUT.stat().st_size} bytes, {transparent} px transparent)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|