Monorepo for Aesthetic.Computer aesthetic.computer
at main 396 lines 15 kB view raw
1{ 2 "cells": [ 3 { 4 "cell_type": "markdown", 5 "metadata": {}, 6 "source": [ 7 "# @fifi thumbs: oven + embedded `#image` checks\n", 8 "\n", 9 "This notebook is for validating thumbnail generation behavior for **`$odj`** and **`$eep`**,\n", 10 "especially whether embedded `#image` references are present in oven renders.\n", 11 "\n", 12 "## What this notebook does\n", 13 "- Pulls live source from `aesthetic.computer/api/store-kidlisp`\n", 14 "- Extracts embedded `#...` references from source\n", 15 "- Resolves painting-code mappings\n", 16 "- Builds production oven URLs with fresh cache keys\n", 17 "- Optionally forces live renders and prints response headers (`x-grab-id`, `x-cache`)\n", 18 "- Shows side-by-side animation/still preview links for manual verification\n" 19 ] 20 }, 21 { 22 "cell_type": "code", 23 "execution_count": null, 24 "metadata": {}, 25 "outputs": [], 26 "source": [ 27 "from __future__ import annotations\n", 28 "\n", 29 "import hashlib\n", 30 "import json\n", 31 "import re\n", 32 "import time\n", 33 "import urllib.parse\n", 34 "import urllib.request\n", 35 "from dataclasses import dataclass\n", 36 "from typing import Dict, List, Any\n", 37 "\n", 38 "from IPython.display import HTML, Markdown, display\n" 39 ] 40 }, 41 { 42 "cell_type": "code", 43 "execution_count": null, 44 "metadata": {}, 45 "outputs": [], 46 "source": [ 47 "# --- Configuration ---\n", 48 "PIECES = ['odj', 'eep']\n", 49 "TOKEN_IDS = {'odj': '28', 'eep': '29'}\n", 50 "\n", 51 "BASE_AC = 'https://aesthetic.computer'\n", 52 "BASE_OVEN = 'https://oven.aesthetic.computer'\n", 53 "BASE_TZKT = 'https://api.tzkt.io/v1'\n", 54 "\n", 55 "# Flip this to True when you want to force fresh generation (can take ~10-40s/piece).\n", 56 "FORCE_GENERATE = False\n", 57 "\n", 58 "# Generation parameters aligned with keep pipeline style.\n", 59 "GEN = {\n", 60 " 'width': 512,\n", 61 " 'height': 512,\n", 62 " 'duration': 8000,\n", 63 " 'fps': 10,\n", 64 " 'quality': 70,\n", 65 " 'density': 2,\n", 66 " 'source': 'keep',\n", 67 " 'skipCache': 'true',\n", 68 "}\n" 69 ] 70 }, 71 { 72 "cell_type": "code", 73 "execution_count": null, 74 "metadata": {}, 75 "outputs": [], 76 "source": [ 77 "def get_json(url: str, timeout: int = 30) -> Dict[str, Any]:\n", 78 " req = urllib.request.Request(url, headers={'User-Agent': 'ac-fifi-thumbs-notebook/1.0'})\n", 79 " with urllib.request.urlopen(req, timeout=timeout) as resp:\n", 80 " return json.loads(resp.read().decode('utf-8'))\n", 81 "\n", 82 "\n", 83 "def get_bytes_with_headers(url: str, timeout: int = 120):\n", 84 " req = urllib.request.Request(url, headers={'User-Agent': 'ac-fifi-thumbs-notebook/1.0'})\n", 85 " with urllib.request.urlopen(req, timeout=timeout) as resp:\n", 86 " body = resp.read()\n", 87 " headers = {k.lower(): v for k, v in resp.headers.items()}\n", 88 " status = getattr(resp, 'status', 200)\n", 89 " return status, headers, body\n", 90 "\n", 91 "\n", 92 "def source_sha16(source: str) -> str:\n", 93 " return hashlib.sha256((source or '').encode('utf-8')).hexdigest()[:16]\n", 94 "\n", 95 "\n", 96 "def extract_embedded_refs(source: str) -> List[str]:\n", 97 " # Captures stamp #txr, paste #abc, etc.\n", 98 " refs = re.findall(r'#([A-Za-z0-9]{3,})', source or '')\n", 99 " # Keep order but dedupe\n", 100 " out = []\n", 101 " seen = set()\n", 102 " for r in refs:\n", 103 " if r not in seen:\n", 104 " out.append(r)\n", 105 " seen.add(r)\n", 106 " return out\n" 107 ] 108 }, 109 { 110 "cell_type": "code", 111 "execution_count": null, 112 "metadata": {}, 113 "outputs": [], 114 "source": [ 115 "# --- Pull live source + piece metadata ---\n", 116 "piece_data = {}\n", 117 "for code in PIECES:\n", 118 " url = f\"{BASE_AC}/api/store-kidlisp?code={urllib.parse.quote(code)}\"\n", 119 " piece_data[code] = get_json(url)\n", 120 "\n", 121 "for code in PIECES:\n", 122 " d = piece_data[code]\n", 123 " print(f\"${code}\")\n", 124 " print(\" handle:\", d.get('handle'))\n", 125 " print(\" hits :\", d.get('hits'))\n", 126 " print(\" when :\", d.get('when'))\n", 127 " print(\" source:\\n\" + (d.get('source') or '').strip())\n", 128 " print()\n" 129 ] 130 }, 131 { 132 "cell_type": "code", 133 "execution_count": null, 134 "metadata": {}, 135 "outputs": [], 136 "source": [ 137 "# --- Extract embedded #image refs + resolve mapping ---\n", 138 "embedded_map = {}\n", 139 "resolved_map = {}\n", 140 "\n", 141 "for code in PIECES:\n", 142 " src = piece_data[code].get('source') or ''\n", 143 " refs = extract_embedded_refs(src)\n", 144 " embedded_map[code] = refs\n", 145 "\n", 146 " resolved = []\n", 147 " for ref in refs:\n", 148 " mapping_url = f\"{BASE_AC}/api/painting-code?code={urllib.parse.quote(ref)}\"\n", 149 " m = get_json(mapping_url)\n", 150 " handle = m.get('handle')\n", 151 " slug = m.get('slug')\n", 152 " media_url = f\"{BASE_AC}/media/paintings/{ref}.png\"\n", 153 " direct_url = f\"{BASE_AC}/media/@{handle}/painting/{slug}.png\" if handle and slug else None\n", 154 " resolved.append({\n", 155 " 'ref': ref,\n", 156 " 'mapping': m,\n", 157 " 'media_url': media_url,\n", 158 " 'direct_url': direct_url,\n", 159 " })\n", 160 " resolved_map[code] = resolved\n", 161 "\n", 162 "for code in PIECES:\n", 163 " print(f\"${code} refs:\", embedded_map[code])\n", 164 " for row in resolved_map[code]:\n", 165 " print(\" \", row['ref'], \"->\", row['mapping'])\n", 166 " print(\" media:\", row['media_url'])\n", 167 " print(\" direct:\", row['direct_url'])\n", 168 " print()\n" 169 ] 170 }, 171 { 172 "cell_type": "code", 173 "execution_count": null, 174 "metadata": {}, 175 "outputs": [], 176 "source": [ 177 "def build_oven_urls(piece_code: str, source: str, ts: int | None = None) -> Dict[str, str]:\n", 178 " ts = int(ts or time.time())\n", 179 " sha16 = source_sha16(source)\n", 180 "\n", 181 " # Use unique cache keys per run so we can force fresh production captures.\n", 182 " cache_anim = f\"rebake-{sha16}-{ts}-{piece_code}\"\n", 183 " cache_still = f\"rebake-{sha16}-{ts}-still-{piece_code}\"\n", 184 "\n", 185 " webp_q = {\n", 186 " 'duration': str(GEN['duration']),\n", 187 " 'fps': str(GEN['fps']),\n", 188 " 'quality': str(GEN['quality']),\n", 189 " 'density': str(GEN['density']),\n", 190 " 'source': GEN['source'],\n", 191 " 'skipCache': GEN['skipCache'],\n", 192 " 'cacheKey': cache_anim,\n", 193 " }\n", 194 " png_q = {\n", 195 " 'density': str(GEN['density']),\n", 196 " 'source': GEN['source'],\n", 197 " 'skipCache': GEN['skipCache'],\n", 198 " 'cacheKey': cache_still,\n", 199 " }\n", 200 "\n", 201 " piece_path = urllib.parse.quote(f\"${piece_code}\")\n", 202 " webp_url = (\n", 203 " f\"{BASE_OVEN}/grab/webp/{GEN['width']}/{GEN['height']}/{piece_path}?\"\n", 204 " + urllib.parse.urlencode(webp_q)\n", 205 " )\n", 206 " png_url = (\n", 207 " f\"{BASE_OVEN}/grab/png/{GEN['width']}/{GEN['height']}/{piece_path}?\"\n", 208 " + urllib.parse.urlencode(png_q)\n", 209 " )\n", 210 "\n", 211 " return {\n", 212 " 'sha16': sha16,\n", 213 " 'cache_anim': cache_anim,\n", 214 " 'cache_still': cache_still,\n", 215 " 'webp_url': webp_url,\n", 216 " 'png_url': png_url,\n", 217 " }\n", 218 "\n", 219 "\n", 220 "oven_urls = {\n", 221 " code: build_oven_urls(code, piece_data[code].get('source') or '')\n", 222 " for code in PIECES\n", 223 "}\n", 224 "\n", 225 "for code in PIECES:\n", 226 " u = oven_urls[code]\n", 227 " print(f\"${code}\")\n", 228 " print(\" sha16 :\", u['sha16'])\n", 229 " print(\" webp_url :\", u['webp_url'])\n", 230 " print(\" png_url :\", u['png_url'])\n", 231 " print()\n" 232 ] 233 }, 234 { 235 "cell_type": "code", 236 "execution_count": null, 237 "metadata": {}, 238 "outputs": [], 239 "source": [ 240 "# --- Optional: force live production renders now ---\n", 241 "# This will call oven and download the response bytes.\n", 242 "\n", 243 "results = {}\n", 244 "\n", 245 "if FORCE_GENERATE:\n", 246 " for code in PIECES:\n", 247 " results[code] = {}\n", 248 " for kind in ['webp_url', 'png_url']:\n", 249 " url = oven_urls[code][kind]\n", 250 " try:\n", 251 " status, headers, body = get_bytes_with_headers(url, timeout=150)\n", 252 " results[code][kind] = {\n", 253 " 'status': status,\n", 254 " 'size': len(body),\n", 255 " 'content-type': headers.get('content-type'),\n", 256 " 'x-grab-id': headers.get('x-grab-id'),\n", 257 " 'x-cache': headers.get('x-cache'),\n", 258 " 'url': url,\n", 259 " }\n", 260 " except Exception as e:\n", 261 " results[code][kind] = {'error': str(e), 'url': url}\n", 262 "\n", 263 " print(json.dumps(results, indent=2))\n", 264 "else:\n", 265 " print('FORCE_GENERATE is False. Set it to True and rerun this cell to force production grabs.')\n" 266 ] 267 }, 268 { 269 "cell_type": "code", 270 "execution_count": null, 271 "metadata": {}, 272 "outputs": [], 273 "source": [ 274 "# --- View links and previews ---\n", 275 "rows = []\n", 276 "for code in PIECES:\n", 277 " refs_html = []\n", 278 " for r in resolved_map[code]:\n", 279 " refs_html.append(\n", 280 " f\"<div><code>#{r['ref']}</code> \"\n", 281 " f\"\u2192 <a href='{r['media_url']}' target='_blank'>media</a> \"\n", 282 " f\"| <a href='{r['direct_url']}' target='_blank'>direct</a></div>\"\n", 283 " )\n", 284 "\n", 285 " webp = oven_urls[code]['webp_url']\n", 286 " png = oven_urls[code]['png_url']\n", 287 "\n", 288 " rows.append(f\"\"\"\n", 289 " <tr>\n", 290 " <td><code>${code}</code></td>\n", 291 " <td>{''.join(refs_html) or '<em>none</em>'}</td>\n", 292 " <td>\n", 293 " <a href='{webp}' target='_blank'>webp url</a><br/>\n", 294 " <img src='{webp}' width='220' style='border:1px solid #ccc; margin-top:6px;' />\n", 295 " </td>\n", 296 " <td>\n", 297 " <a href='{png}' target='_blank'>png url</a><br/>\n", 298 " <img src='{png}' width='220' style='border:1px solid #ccc; margin-top:6px;' />\n", 299 " </td>\n", 300 " </tr>\n", 301 " \"\"\")\n", 302 "\n", 303 "html = f\"\"\"\n", 304 "<style>\n", 305 " table.fifi-check {{ border-collapse: collapse; width: 100%; }}\n", 306 " .fifi-check th, .fifi-check td {{ border: 1px solid #ddd; padding: 8px; vertical-align: top; }}\n", 307 " .fifi-check th {{ background: #f6f8fa; text-align: left; }}\n", 308 "</style>\n", 309 "<table class='fifi-check'>\n", 310 " <thead>\n", 311 " <tr>\n", 312 " <th>piece</th>\n", 313 " <th>embedded refs</th>\n", 314 " <th>animated webp</th>\n", 315 " <th>still png</th>\n", 316 " </tr>\n", 317 " </thead>\n", 318 " <tbody>\n", 319 " {''.join(rows)}\n", 320 " </tbody>\n", 321 "</table>\n", 322 "\"\"\"\n", 323 "\n", 324 "display(HTML(html))\n" 325 ] 326 }, 327 { 328 "cell_type": "code", 329 "execution_count": null, 330 "metadata": {}, 331 "outputs": [], 332 "source": [ 333 "# --- On-chain metadata for fifi token IDs (28, 29) ---\n", 334 "contract = 'KT1Q1irsjSZ7EfUN4qHzAB2t7xLBPsAWYwBB'\n", 335 "\n", 336 "def ipfs_to_gateway(uri: str) -> str:\n", 337 " if not uri or not isinstance(uri, str):\n", 338 " return ''\n", 339 " if uri.startswith('ipfs://'):\n", 340 " cid = uri.replace('ipfs://', '').split('/')[0]\n", 341 " return f'https://ipfs.aesthetic.computer/ipfs/{cid}'\n", 342 " return uri\n", 343 "\n", 344 "chain = {}\n", 345 "for code, token_id in TOKEN_IDS.items():\n", 346 " url = f\"{BASE_TZKT}/tokens?contract={contract}&tokenId={token_id}\"\n", 347 " payload = get_json(url)\n", 348 " row = payload[0] if payload else {}\n", 349 " md = row.get('metadata', {})\n", 350 " chain[code] = {\n", 351 " 'token_id': token_id,\n", 352 " 'name': md.get('name'),\n", 353 " 'thumbnailUri': md.get('thumbnailUri'),\n", 354 " 'artifactUri': md.get('artifactUri'),\n", 355 " 'displayUri': md.get('displayUri'),\n", 356 " 'description': md.get('description'),\n", 357 " 'thumbnailGateway': ipfs_to_gateway(md.get('thumbnailUri')),\n", 358 " }\n", 359 "\n", 360 "print(json.dumps(chain, indent=2))\n" 361 ] 362 }, 363 { 364 "cell_type": "markdown", 365 "metadata": {}, 366 "source": [ 367 "## Notes\n", 368 "\n", 369 "- This notebook intentionally uses live production endpoints.\n", 370 "- Use unique `cacheKey` values when testing so you can confirm fresh oven output.\n", 371 "- If you want end-to-end keep regeneration, run your keep pipeline after validating the URLs here.\n" 372 ] 373 } 374 ], 375 "metadata": { 376 "kernelspec": { 377 "display_name": "Python 3", 378 "language": "python", 379 "name": "python3" 380 }, 381 "language_info": { 382 "name": "python", 383 "version": "3.11.13", 384 "mimetype": "text/x-python", 385 "codemirror_mode": { 386 "name": "ipython", 387 "version": 3 388 }, 389 "pygments_lexer": "ipython3", 390 "nbconvert_exporter": "python", 391 "file_extension": ".py" 392 } 393 }, 394 "nbformat": 4, 395 "nbformat_minor": 5 396}