diff --git a/Black Pepper Tofu (Ottolenghi).cook b/Black Pepper Tofu (Ottolenghi).cook index b26d435..06eaa3c 100644 --- a/Black Pepper Tofu (Ottolenghi).cook +++ b/Black Pepper Tofu (Ottolenghi).cook @@ -1,3 +1,12 @@ +--- +title: Black Pepper Tofu (Ottolenghi) +description: +servings: 1 +category: uncategorized +source: Unknown +image: https://roux.roo.lol/recipe-images/black-pepper-tofu-ottolenghi.webp +--- + >> Servings: 4 Pour enough @vegetable oil{} into a large #frying pan or wok{} to come 1/4 inch and turn on heat. diff --git a/Black Pepper Tofu with Bok Choy.cook b/Black Pepper Tofu with Bok Choy.cook index 8085877..2f9d180 100644 --- a/Black Pepper Tofu with Bok Choy.cook +++ b/Black Pepper Tofu with Bok Choy.cook @@ -1,3 +1,12 @@ +--- +title: Black Pepper Tofu with Bok Choy +description: +servings: 1 +category: uncategorized +source: Unknown +image: https://roux.roo.lol/recipe-images/black-pepper-tofu-with-bok-choy.webp +--- + >> Servings: 2 >> Prep time: 30 minutes >> Total time: 30 minutes diff --git a/Caramel Cake.cook b/Caramel Cake.cook index d307974..4bfa755 100644 --- a/Caramel Cake.cook +++ b/Caramel Cake.cook @@ -1,3 +1,12 @@ +--- +title: Caramel Cake +description: +servings: 1 +category: uncategorized +source: Unknown +image: https://roux.roo.lol/recipe-images/caramel-cake.webp +--- + >> Servings: 10 Combine @sour cream{8 ounces} and @milk{1/4 cup}. diff --git a/Caramel Icing.cook b/Caramel Icing.cook index 316763d..735d087 100644 --- a/Caramel Icing.cook +++ b/Caramel Icing.cook @@ -1,3 +1,12 @@ +--- +title: Caramel Icing +description: +servings: 1 +category: uncategorized +source: Unknown +image: https://roux.roo.lol/recipe-images/caramel-icing.webp +--- + >> Servings: 10 Sprinkle @sugar{1/2 cup} in a shallow heavy #3½-quart Dutch oven{}. diff --git a/Italian Sausage and Delicata Rigatoni.cook b/Italian Sausage and Delicata Rigatoni.cook index 7dd673f..1e878aa 100644 --- a/Italian Sausage and Delicata Rigatoni.cook +++ b/Italian Sausage and Delicata Rigatoni.cook @@ -1,3 +1,12 @@ +--- +title: Italian Sausage & Delicata Rigatoni +description: +servings: 1 +category: uncategorized +source: Unknown +image: https://roux.roo.lol/recipe-images/italian-sausage-and-delicata-rigatoni.webp +--- + >> title: Italian Sausage & Delicata Rigatoni >> author: Chef Robin Pridgen >> servings: 2 diff --git a/bin/task3-frontmatter.py b/bin/task3-frontmatter.py new file mode 100644 index 0000000..149392c --- /dev/null +++ b/bin/task3-frontmatter.py @@ -0,0 +1,156 @@ +#!/tmp/recipe-venv/bin/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() diff --git a/butternut-turkey-chili.cook b/butternut-turkey-chili.cook index b3e305d..ed5d9e9 100644 --- a/butternut-turkey-chili.cook +++ b/butternut-turkey-chili.cook @@ -1,3 +1,12 @@ +--- +title: Butternut & Turkey Chili +description: +servings: 1 +category: uncategorized +source: Unknown +image: https://roux.roo.lol/recipe-images/butternut-turkey-chili.webp +--- + >> title: Butternut & Turkey Chili >> author: Chef Torie Cox >> servings: 2 diff --git a/example-fried-rice.cook b/example-fried-rice.cook index 2f289c5..93932ed 100644 --- a/example-fried-rice.cook +++ b/example-fried-rice.cook @@ -1,3 +1,12 @@ +--- +title: Fried Rice +description: +servings: 1 +category: uncategorized +source: Unknown +image: https://roux.roo.lol/recipe-images/example-fried-rice.webp +--- + -- TODO add source Mix together @oyster sauce{1%tbsp}, @soy sauce{5%tbsp} and @sesame oil{5%tsp}, set aside. diff --git a/indian-spiced-chicken.cook b/indian-spiced-chicken.cook index 33f4d3b..166e8c1 100644 --- a/indian-spiced-chicken.cook +++ b/indian-spiced-chicken.cook @@ -1,3 +1,12 @@ +--- +title: Indian Spiced Chicken +description: +servings: 1 +category: uncategorized +source: Unknown +image: https://roux.roo.lol/recipe-images/indian-spiced-chicken.webp +--- + >> source: Gather and Garnish >> time required: 35 minutes diff --git a/recipe-images/black-pepper-tofu-ottolenghi.webp b/recipe-images/black-pepper-tofu-ottolenghi.webp new file mode 100644 index 0000000..b1a05ba Binary files /dev/null and b/recipe-images/black-pepper-tofu-ottolenghi.webp differ diff --git a/recipe-images/black-pepper-tofu-with-bok-choy.webp b/recipe-images/black-pepper-tofu-with-bok-choy.webp new file mode 100644 index 0000000..6805f9c Binary files /dev/null and b/recipe-images/black-pepper-tofu-with-bok-choy.webp differ diff --git a/recipe-images/butternut-turkey-chili.webp b/recipe-images/butternut-turkey-chili.webp new file mode 100644 index 0000000..d5282f6 Binary files /dev/null and b/recipe-images/butternut-turkey-chili.webp differ diff --git a/recipe-images/caramel-cake.webp b/recipe-images/caramel-cake.webp new file mode 100644 index 0000000..b89570e Binary files /dev/null and b/recipe-images/caramel-cake.webp differ diff --git a/recipe-images/caramel-icing.webp b/recipe-images/caramel-icing.webp new file mode 100644 index 0000000..361e20f Binary files /dev/null and b/recipe-images/caramel-icing.webp differ diff --git a/recipe-images/example-fried-rice.webp b/recipe-images/example-fried-rice.webp new file mode 100644 index 0000000..73d36cf Binary files /dev/null and b/recipe-images/example-fried-rice.webp differ diff --git a/recipe-images/indian-spiced-chicken.webp b/recipe-images/indian-spiced-chicken.webp new file mode 100644 index 0000000..acc9f17 Binary files /dev/null and b/recipe-images/indian-spiced-chicken.webp differ diff --git a/recipe-images/italian-sausage-and-delicata-rigatoni.webp b/recipe-images/italian-sausage-and-delicata-rigatoni.webp new file mode 100644 index 0000000..fb88bb5 Binary files /dev/null and b/recipe-images/italian-sausage-and-delicata-rigatoni.webp differ diff --git a/recipe-images/shrimp-feta.webp b/recipe-images/shrimp-feta.webp new file mode 100644 index 0000000..a6a0577 Binary files /dev/null and b/recipe-images/shrimp-feta.webp differ diff --git a/shrimp-feta.cook b/shrimp-feta.cook index 23cbc0a..ef1b908 100644 --- a/shrimp-feta.cook +++ b/shrimp-feta.cook @@ -1,3 +1,12 @@ +--- +title: Orzo with shrimp, tomato and marinated feta +description: +servings: 1 +category: uncategorized +source: Unknown +image: https://roux.roo.lol/recipe-images/shrimp-feta.webp +--- + >> title: Orzo with shrimp, tomato and marinated feta >> source: https://thehappyfoodie.co.uk/recipes/ottolenghis-orzo-with-prawns-tomato-and-marinated-feta/ >> servings: 4