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)