Initial release of Moniki paid services site and CMS (v1.0.0).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user