Initial commit: CookIT recipe collection and week planner
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
7d7a0bc60b
173 files changed
+22803
No files matched your search
@@ -0,0 +1,58 @@
|
||||
"""Writes a Recipe Bundle: a zip with bundle.json and downscaled photos (see docs/recipe-bundle-format.md)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import json
|
||||
import zipfile
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from PIL import Image, ImageOps, UnidentifiedImageError
|
||||
|
||||
FORMAT = "cookit.recipe-bundle"
|
||||
VERSION = 1
|
||||
MAX_PHOTO_EDGE = 1600
|
||||
JPEG_QUALITY = 82
|
||||
|
||||
|
||||
def downscale(data: bytes) -> bytes | None:
|
||||
"""JPEG no larger than MAX_PHOTO_EDGE on its long side, or None if the picture can't be read."""
|
||||
try:
|
||||
img = Image.open(io.BytesIO(data))
|
||||
img = ImageOps.exif_transpose(img)
|
||||
except (UnidentifiedImageError, OSError, ValueError):
|
||||
return None
|
||||
if img.mode in ("RGBA", "LA", "P"):
|
||||
img = img.convert("RGBA")
|
||||
background = Image.new("RGB", img.size, "white")
|
||||
background.paste(img, mask=img.getchannel("A"))
|
||||
img = background
|
||||
elif img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
img.thumbnail((MAX_PHOTO_EDGE, MAX_PHOTO_EDGE), Image.Resampling.LANCZOS)
|
||||
out = io.BytesIO()
|
||||
img.save(out, "JPEG", quality=JPEG_QUALITY, optimize=True)
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
def write_bundle(path: str, recipes: list[tuple[dict, list[bytes]]]) -> None:
|
||||
"""recipes: (recipe dict, raw photo bytes in display order)."""
|
||||
with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as z:
|
||||
entries = []
|
||||
for recipe, photos in recipes:
|
||||
recipe["photos"] = []
|
||||
for raw in photos:
|
||||
jpeg = downscale(raw)
|
||||
if jpeg is None:
|
||||
continue
|
||||
name = f"photos/{recipe['id']}-{len(recipe['photos']) + 1}.jpg"
|
||||
z.writestr(zipfile.ZipInfo(name, date_time=(2026, 1, 1, 0, 0, 0)), jpeg, zipfile.ZIP_STORED)
|
||||
recipe["photos"].append(name)
|
||||
entries.append(recipe)
|
||||
manifest = {
|
||||
"format": FORMAT,
|
||||
"version": VERSION,
|
||||
"createdAt": datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
|
||||
"recipes": entries,
|
||||
}
|
||||
z.writestr("bundle.json", json.dumps(manifest, ensure_ascii=False, indent=2))
|
||||
Reference in new issue
Block a user