feat: cover generation with Pollinations.ai and text fallback

Made-with: Cursor
This commit is contained in:
cottongin
2026-04-06 15:12:01 -04:00
parent 46796b8bf8
commit d88a0817b7
2 changed files with 200 additions and 0 deletions

123
src/cover.py Normal file
View File

@@ -0,0 +1,123 @@
import logging
import os
from datetime import date
from io import BytesIO
from urllib.parse import quote
import requests
from PIL import Image as PILImage, ImageDraw, ImageFont
import config
from src.images import _resize_to_fit
logger = logging.getLogger(__name__)
def _get_font(size: int) -> ImageFont.FreeTypeFont:
try:
return ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", size)
except OSError:
pass
try:
return ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", size)
except OSError:
pass
return ImageFont.load_default()
def generate_text_cover(
output_dir: str,
week_start: date,
week_end: date,
headlines: list[str],
) -> str:
os.makedirs(output_dir, exist_ok=True)
img = PILImage.new("RGB", (800, 480), color="white")
draw = ImageDraw.Draw(img)
title_font = _get_font(36)
date_font = _get_font(20)
headline_font = _get_font(16)
draw.text((400, 30), "Plymouth Independent", fill="black",
font=title_font, anchor="mt")
date_str = f"Week of {week_start.strftime('%b %d')} \u2013 {week_end.strftime('%b %d, %Y')}"
draw.text((400, 80), date_str, fill="gray", font=date_font, anchor="mt")
draw.line([(50, 110), (750, 110)], fill="black", width=2)
y = 130
for i, headline in enumerate(headlines[:8]):
if y > 440:
break
prefix = f"\u2022 {headline}"
if len(prefix) > 70:
prefix = prefix[:67] + "..."
draw.text((60, y), prefix, fill="black", font=headline_font)
y += 35
filename = f"cover-{week_start.isoformat()}-text.jpg"
path = os.path.join(output_dir, filename)
img.save(path, format="JPEG", progressive=False, quality=90)
return path
def generate_ai_cover(
output_dir: str,
week_start: date,
week_end: date,
headlines: list[str],
) -> str:
os.makedirs(output_dir, exist_ok=True)
top_headlines = ", ".join(headlines[:3])
prompt = (
f"Newspaper front page illustration for Plymouth Massachusetts local news. "
f"Headlines: {top_headlines}. "
f"Classic broadsheet newspaper style, black and white ink drawing, editorial illustration."
)
url = config.POLLINATIONS_URL.format(prompt=quote(prompt))
try:
response = requests.get(url, timeout=60)
response.raise_for_status()
img = PILImage.open(BytesIO(response.content))
if img.mode != "RGB":
img = img.convert("RGB")
img = _resize_to_fit(img)
draw = ImageDraw.Draw(img)
title_font = _get_font(28)
date_font = _get_font(16)
draw.text((img.width // 2, 15), "Plymouth Independent",
fill="white", font=title_font, anchor="mt",
stroke_width=2, stroke_fill="black")
date_str = f"Week of {week_start.strftime('%b %d')} \u2013 {week_end.strftime('%b %d, %Y')}"
draw.text((img.width // 2, 50), date_str,
fill="white", font=date_font, anchor="mt",
stroke_width=1, stroke_fill="black")
filename = f"cover-{week_start.isoformat()}-ai.jpg"
path = os.path.join(output_dir, filename)
img.save(path, format="JPEG", progressive=False, quality=90)
return path
except Exception as e:
logger.error("AI cover generation failed, falling back to text: %s", e)
return generate_text_cover(output_dir, week_start, week_end, headlines)
def generate_cover(
method: str,
output_dir: str,
week_start: date,
week_end: date,
headlines: list[str],
) -> str:
if method == "ai":
return generate_ai_cover(output_dir, week_start, week_end, headlines)
return generate_text_cover(output_dir, week_start, week_end, headlines)