#!/usr/bin/env python3 """Add frontmatter and images to 9 recipes without frontmatter.""" import re, sys, time from pathlib import Path import requests from bs4 import BeautifulSoup from ddgs import DDGS WORKDIR = Path("/home/jbrechtel/recipes/.worktrees/recipe-images") HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"} # (filename, slug, title, source_url_or_empty) recipes = [ ("Black Pepper Tofu (Ottolenghi).cook", "black-pepper-tofu-ottolenghi", "Black Pepper Tofu (Ottolenghi)", ""), ("Black Pepper Tofu with Bok Choy.cook", "black-pepper-tofu-with-bok-choy", "Black Pepper Tofu with Bok Choy", ""), ("butternut-turkey-chili.cook", "butternut-turkey-chili", "Butternut & Turkey Chili", ""), ("Caramel Cake.cook", "caramel-cake", "Caramel Cake", ""), ("Caramel Icing.cook", "caramel-icing", "Caramel Icing", ""), ("example-fried-rice.cook", "example-fried-rice", "Fried Rice", ""), ("indian-spiced-chicken.cook", "indian-spiced-chicken", "Indian Spiced Chicken", ""), ("Italian Sausage and Delicata Rigatoni.cook", "italian-sausage-and-delicata-rigatoni", "Italian Sausage & Delicata Rigatoni", ""), ("shrimp-feta.cook", "shrimp-feta", "Orzo with shrimp, tomato and marinated feta", "https://thehappyfoodie.co.uk/recipes/ottolenghis-orzo-with-prawns-tomato-and-marinated-feta/"), ] def fetch_og_image(url: str) -> str | None: try: resp = requests.get(url, headers=HEADERS, timeout=15) resp.raise_for_status() except: return None soup = BeautifulSoup(resp.text, "html.parser") for meta in soup.find_all("meta"): c = meta.get("content", "") p = (meta.get("property") or "").lower() n = (meta.get("name") or "").lower() if ("og:image" in p or "og:image" in n or "twitter:image" in n) and c: return c # JSON-LD for script in soup.find_all("script", type="application/ld+json"): try: import json data = json.loads(script.string) items = data if isinstance(data, list) else [data] for item in items: if isinstance(item, dict) and item.get("@type") == "Recipe": img = item.get("image") if isinstance(img, str): return img if isinstance(img, list): for i in img: if isinstance(i, str): return i if isinstance(i, dict): u = i.get("contentUrl") or i.get("url", "") if u: return u if isinstance(img, dict): u = img.get("contentUrl") or img.get("url", "") if u: return u except: pass return None def search_and_scrape(query: str) -> str | None: try: results = list(DDGS().text(query, max_results=5)) except: return None for r in results: url = r.get("href", "") if not url or "pinterest" in url or "facebook" in url: continue img = fetch_og_image(url) if img: return img time.sleep(0.3) return None def download_image(url: str, dest: Path) -> bool: try: resp = requests.get(url, headers=HEADERS, timeout=30) resp.raise_for_status() ct = resp.headers.get("content-type", "") if not ct.startswith("image/"): return False content = resp.content if len(content) < 5000: return False dest.write_bytes(content) return True except: return False def add_frontmatter(filepath: Path, slug: str, title: str): url = f"https://roux.roo.lol/recipe-images/{slug}.webp" content = filepath.read_text(encoding="utf-8") frontmatter = f"""--- title: {title} description: servings: 1 category: uncategorized source: Unknown image: {url} --- """ filepath.write_text(frontmatter + content, encoding="utf-8") print(f" Added frontmatter to {filepath.name}", file=sys.stderr) def main(): for filename, slug, title, src_url in recipes: fp = WORKDIR / filename dest = WORKDIR / "recipe-images" / f"{slug}.webp" print(f"[{slug}] Processing {filename}", file=sys.stderr) # Find and download image img_url = None if src_url: print(f" Trying source URL: {src_url}", file=sys.stderr) img_url = fetch_og_image(src_url) time.sleep(0.3) if not img_url: print(f" Searching: {title} recipe", file=sys.stderr) img_url = search_and_scrape(f"{title} recipe") time.sleep(0.3) if not img_url: print(f" Searching (alt): {title}", file=sys.stderr) img_url = search_and_scrape(title) time.sleep(0.3) if img_url: print(f" Found: {img_url[:80]}", file=sys.stderr) if download_image(img_url, dest): print(f" Downloaded ({dest.stat().st_size} bytes)", file=sys.stderr) else: print(f" Download failed, will still add frontmatter", file=sys.stderr) else: print(f" No image found, will still add frontmatter", file=sys.stderr) # Add frontmatter (with or without image) add_frontmatter(fp, slug, title) print(f" Done", file=sys.stderr) print("\n=== Task 3 complete ===", file=sys.stderr) if __name__ == "__main__": main()