125 lines
5.5 KiB
Python
125 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Download official MONIKI leadership photos and normalize them to 720x960 JPEG."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
OUT_DIR = ROOT / "public" / "organization" / "leaders"
|
|
TARGET_W, TARGET_H = 720, 960
|
|
RATIO = TARGET_W / TARGET_H
|
|
|
|
LEADERS = [
|
|
("sobolev", "https://www.monikiweb.ru/sites/default/files/page_content_files/!!!Sobolev.JPG"),
|
|
("gameeva", "https://www.monikiweb.ru/sites/default/files/page_content_files/GameevaEV2026-07.jpg"),
|
|
("stepanova", "https://www.monikiweb.ru/sites/default/files/page_content_files/StepanovaAM.jpg"),
|
|
("kakorina", "https://www.monikiweb.ru/sites/default/files/page_content_files/!!!Kakorina_E_P.jpg"),
|
|
("mozhaev", "https://www.monikiweb.ru/sites/default/files/page_content_files/MozhaevYA.jpg"),
|
|
("chernyavskaya", "https://www.monikiweb.ru/sites/default/files/page_content_files/!!!Chernyavskaya_T_K.jpg"),
|
|
("kononova", "https://www.monikiweb.ru/sites/default/files/page_content_files/KononovaIV.jpg"),
|
|
("bogdan", "https://www.monikiweb.ru/sites/default/files/page_content_files/BogdanAA.png"),
|
|
("lopotovsky", "https://www.monikiweb.ru/sites/default/files/page_content_files/LopotovskyP.jpg"),
|
|
("varlamova", "https://www.monikiweb.ru/sites/default/files/page_content_files/!!!Varlamova_L_S.JPG"),
|
|
("ermolaeva", "https://www.monikiweb.ru/sites/default/files/page_content_files/!!!Ermolaeva_A_V.JPG"),
|
|
("shikina", "https://www.monikiweb.ru/sites/default/files/page_content_files/Shikina.JPG"),
|
|
("padalko", "https://www.monikiweb.ru/sites/default/files/page_content_files/!!!Zam.dir.po_kadram-small.jpg"),
|
|
("simonenkov", "https://www.monikiweb.ru/sites/default/files/simanenkov.JPG"),
|
|
("khizgiaev", None),
|
|
("lobanova", "https://www.monikiweb.ru/sites/default/files/page_content_files/LobanovaOS.jpg.jfif"),
|
|
("zyuzina", "https://www.monikiweb.ru/sites/default/files/page_content_files/photozyzina.jpg"),
|
|
("averin", "https://www.monikiweb.ru/sites/default/files/page_content_files/AverinAA.jpg"),
|
|
("arkhipova", "https://www.monikiweb.ru/sites/default/files/page_content_files/Arkhipova.jpg"),
|
|
("babaeva", "https://www.monikiweb.ru/sites/default/files/page_content_files/BabaevaLA.jpg"),
|
|
("doronina", "https://www.monikiweb.ru/sites/default/files/page_content_files/DoroninaOV.jpg"),
|
|
("kanina", "https://www.monikiweb.ru/sites/default/files/page_content_files/!!!Kanina_E_V.JPG"),
|
|
("kuidin", "https://www.monikiweb.ru/sites/default/files/page_content_files/KuidinDV.jpg"),
|
|
]
|
|
|
|
KHIZGIAEV_CANDIDATES = [
|
|
"https://www.monikiweb.ru/sites/default/files/page_content_files/Hizgiaev.jpg",
|
|
"https://www.monikiweb.ru/sites/default/files/page_content_files/Hizgiyaev.jpg",
|
|
"https://www.monikiweb.ru/sites/default/files/page_content_files/Khizgiaev.jpg",
|
|
"https://www.monikiweb.ru/sites/default/files/page_content_files/!!!Hizgiaev.jpg",
|
|
"https://www.monikiweb.ru/sites/default/files/page_content_files/!!!Hizgiaev_V_I.jpg",
|
|
"https://www.monikiweb.ru/sites/default/files/page_content_files/HizgiaevVI.jpg",
|
|
]
|
|
|
|
|
|
def fetch(url: str) -> bytes | None:
|
|
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=60) as res:
|
|
if res.status != 200:
|
|
return None
|
|
data = res.read()
|
|
if not data:
|
|
return None
|
|
return data
|
|
except Exception as exc: # noqa: BLE001
|
|
print(f" skip {url}: {exc}")
|
|
return None
|
|
|
|
|
|
def normalize(image: Image.Image) -> Image.Image:
|
|
image = ImageOps.exif_transpose(image).convert("RGB")
|
|
width, height = image.size
|
|
if width / height > RATIO:
|
|
crop_w = int(height * RATIO)
|
|
left = max((width - crop_w) // 2, 0)
|
|
image = image.crop((left, 0, left + crop_w, height))
|
|
else:
|
|
crop_h = int(width / RATIO)
|
|
image = image.crop((0, 0, width, min(crop_h, height)))
|
|
return image.resize((TARGET_W, TARGET_H), Image.Resampling.LANCZOS)
|
|
|
|
|
|
def save_jpeg(image: Image.Image, path: Path) -> None:
|
|
image.save(path, "JPEG", quality=84, optimize=True, progressive=True)
|
|
|
|
|
|
def placeholder(initials: str) -> Image.Image:
|
|
image = Image.new("RGB", (TARGET_W, TARGET_H), (232, 240, 232))
|
|
draw = ImageDraw.Draw(image)
|
|
font = ImageFont.load_default()
|
|
try:
|
|
font = ImageFont.truetype("/System/Library/Fonts/Supplemental/Arial Bold.ttf", 140)
|
|
except OSError:
|
|
pass
|
|
bbox = draw.textbbox((0, 0), initials, font=font)
|
|
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
|
draw.text(((TARGET_W - tw) / 2, (TARGET_H - th) / 2), initials, fill=(15, 81, 50), font=font)
|
|
return image
|
|
|
|
|
|
def main() -> None:
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
for slug, url in LEADERS:
|
|
out = OUT_DIR / f"{slug}.jpg"
|
|
print(slug)
|
|
data = None
|
|
if slug == "khizgiaev":
|
|
for candidate in KHIZGIAEV_CANDIDATES:
|
|
data = fetch(candidate)
|
|
if data:
|
|
print(f" found {candidate}")
|
|
break
|
|
elif url:
|
|
data = fetch(url)
|
|
|
|
if data:
|
|
image = Image.open(io.BytesIO(data))
|
|
save_jpeg(normalize(image), out)
|
|
print(f" wrote {out.name} {out.stat().st_size} bytes")
|
|
else:
|
|
save_jpeg(placeholder("ВИХ"), out)
|
|
print(f" placeholder {out.name}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|