Files
ad/molodetz/gallery.py
T

97 lines
3.9 KiB
Python

# retoor <retoor@molodetz.nl>
import io
import logging
import imagehash
from PIL import Image
from molodetz import config
from molodetz.attachments import blob_path, blob_url, write_blob
from molodetz.database import generate_uid, media_by_kind, upsert_media
from molodetz.database.media import get_media_by_file
logger = logging.getLogger(__name__)
THUMB_WIDTH = 720
DUPLICATE_DISTANCE = 2
GALLERY = (
("flyer-keuzes.jpg", "flyer", "Eenendertig keuzes. Jij hebt er nul."),
("flyer-chat.jpg", "flyer", "Een chat is geen portfolio."),
("flyer-bijna.jpg", "flyer", "Bijna is geen staat."),
("flyer-assistent.jpg", "flyer", "De assistent oordeelt niet."),
("meme-vloer.jpg", "meme", "Een zesje is prima. De vloer, bedoel je."),
("meme-split.jpg", "meme", "Stuur ze naar huis."),
("meme-cursus.jpg", "meme", "Geen ebook. Wel afmaken."),
("take-chatlog.jpg", "meme", "Je portfolio is een chatlog."),
("take-copilot.jpg", "meme", "Copilot is geen senior."),
("take-coping.jpg", "meme", "Een zesje is coping."),
("poster-thuis.jpg", "meme", "Vibe coders, ga naar huis."),
("poster-vloer.jpg", "meme", "Een zesje? Dat is de vloer."),
("poster-cursus.jpg", "meme", "Geen cursus. Wel afmaken."),
("poster-luiheid.jpg", "meme", "AI pakt je baan niet. Luiheid wel."),
("poster-spel.jpg", "meme", "Het spel is om. Jij bent het niet."),
("story.jpg", "meme", "Geen ebook. Geen x10. Afwerken wel."),
("meme-zes.jpg", "meme", "Een zesje. Gefeliciteerd, je bent gemiddeld."),
("meme-gesprek.jpg", "meme", "Mijn portfolio is een gesprek."),
("meme-product.jpg", "meme", "De cursus is het product."),
("meme-bijna.jpg", "meme", "Iedereen staat bij bijna."),
("meme-compiles.jpg", "meme", "It compiles. Ship it, king."),
("meme-senior.jpg", "meme", "My copilot is the senior."),
("meme-trophy.jpg", "meme", "Peak performance, 6 out of 10."),
("meme-todo.jpg", "meme", "Side projects, a love story."),
)
def _thumbnail_bytes(image):
copy = image.convert("RGB")
copy.thumbnail((THUMB_WIDTH, THUMB_WIDTH * 2))
buffer = io.BytesIO()
copy.save(buffer, format="WEBP", quality=82, method=4)
return buffer.getvalue()
def sync_gallery():
seen_hashes = []
synced = 0
for position, (filename, kind, caption) in enumerate(GALLERY):
source = config.MEDIA_SOURCE_DIR / filename
if not source.exists():
logger.warning("gallery source missing: %s", source)
continue
existing = get_media_by_file(filename)
with Image.open(source) as image:
width, height = image.size
phash = imagehash.phash(image)
if any(phash - other <= DUPLICATE_DISTANCE for other in seen_hashes):
logger.info("gallery duplicate skipped: %s", filename)
continue
seen_hashes.append(phash)
thumb_directory = existing.get("thumb_directory") if existing else None
thumb_name = existing.get("thumb_name") if existing else None
if not thumb_directory or not blob_path(thumb_directory, thumb_name).exists():
thumb_directory, thumb_name = write_blob(
existing["uid"] if existing else generate_uid(), "webp", _thumbnail_bytes(image)
)
upsert_media(filename, kind, caption, position, width, height, str(phash), thumb_directory, thumb_name)
synced += 1
return synced
def gallery_items(kind):
items = []
for row in media_by_kind(kind):
thumb = blob_url(row["thumb_directory"], row["thumb_name"]) if row.get("thumb_directory") else None
items.append(
{
"uid": row["uid"],
"filename": row["filename"],
"caption": row["caption"],
"width": row["width"],
"height": row["height"],
"source_path": f"/static/media/{row['filename']}",
"thumb_url": thumb,
}
)
return items