← back to Wallco Ai
build-mural-master: fix SDXL outpaint — UA + files-API upload + version-pinned endpoint
109602481699e46c0c3b54969e26efdf7f425c4b · 2026-06-03 13:40:23 -0700 · Steve Abrams
Three fixes so the generative bleed actually runs: (1) Replicate's Cloudflare edge
403s the default Python-urllib User-Agent on files/model endpoints → install a
browser-ish UA process-wide (covers build-tif's esrgan calls too); (2) upload
images to the Replicate files API and pass URLs instead of oversized inline base64;
(3) resolve latest_version then POST /v1/predictions (the model-predictions endpoint
404s). Validated isolated on 27151: natural sky/grass bleed, no mirror artifact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M scripts/build-mural-master.py
Diff
commit 109602481699e46c0c3b54969e26efdf7f425c4b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jun 3 13:40:23 2026 -0700
build-mural-master: fix SDXL outpaint — UA + files-API upload + version-pinned endpoint
Three fixes so the generative bleed actually runs: (1) Replicate's Cloudflare edge
403s the default Python-urllib User-Agent on files/model endpoints → install a
browser-ish UA process-wide (covers build-tif's esrgan calls too); (2) upload
images to the Replicate files API and pass URLs instead of oversized inline base64;
(3) resolve latest_version then POST /v1/predictions (the model-predictions endpoint
404s). Validated isolated on 27151: natural sky/grass bleed, no mirror artifact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/build-mural-master.py | 56 +++++++++++++++++++++++++++++++++++--------
1 file changed, 46 insertions(+), 10 deletions(-)
diff --git a/scripts/build-mural-master.py b/scripts/build-mural-master.py
index 6045434..604f539 100644
--- a/scripts/build-mural-master.py
+++ b/scripts/build-mural-master.py
@@ -55,6 +55,14 @@ _bt_spec = importlib.util.spec_from_file_location(
_bt = importlib.util.module_from_spec(_bt_spec)
_bt_spec.loader.exec_module(_bt)
+# Replicate's Cloudflare edge 403s the default `Python-urllib/x` User-Agent on
+# some endpoints (files upload, model GET). Install a browser-ish UA process-
+# wide so every urllib call (here AND in build-tif's _esrgan_x4) gets through.
+import urllib.request as _ur
+_op = _ur.build_opener()
+_op.addheaders = [("User-Agent", "Mozilla/5.0 (Macintosh) wallco-mural/1.0")]
+_ur.install_opener(_op)
+
DPI = _bt.DPI # 150
_HENRY = Path("/Volumes/Henry/wallco-ai-archive/mural-masters")
@@ -131,13 +139,21 @@ _ESRGAN_MAX_IN = 1448 # Replicate real-esrgan GPU cap (~2.09M px input)
def _replicate_run_latest(model, inp, token, poll_max=600):
- """Run a Replicate model's LATEST version (no pinned hash) → output URL(s)."""
+ """Run a Replicate model's latest version via the version-pinned
+ /v1/predictions path (the model-predictions endpoint 404s for some models;
+ this resolves latest_version then POSTs like the working real-esrgan call)."""
import urllib.request, json as _json
+ greq = urllib.request.Request(
+ f"https://api.replicate.com/v1/models/{model}",
+ headers={"Authorization": "Bearer " + token})
+ ver = (_json.loads(urllib.request.urlopen(greq, timeout=30).read())
+ .get("latest_version") or {}).get("id")
+ if not ver:
+ raise RuntimeError(f"{model}: no latest_version")
req = urllib.request.Request(
- f"https://api.replicate.com/v1/models/{model}/predictions",
- data=_json.dumps({"input": inp}).encode(),
- headers={"Authorization": "Bearer " + token, "Content-Type": "application/json",
- "Prefer": "wait"})
+ "https://api.replicate.com/v1/predictions",
+ data=_json.dumps({"version": ver, "input": inp}).encode(),
+ headers={"Authorization": "Bearer " + token, "Content-Type": "application/json"})
pred = _json.loads(urllib.request.urlopen(req, timeout=90).read())
t0 = time.time()
while pred.get("status") not in ("succeeded", "failed", "canceled"):
@@ -156,6 +172,28 @@ def _fetch_img(url):
return Image.open(io.BytesIO(urllib.request.urlopen(url, timeout=180).read())).convert("RGB")
+def _replicate_upload(im, token, name="img.png"):
+ """Upload a PIL image to the Replicate files API → served URL. Large inline
+ base64 in the prediction body gets 403'd by Replicate's edge; uploaded URLs
+ don't. Returns the get-URL to pass as a model input."""
+ import urllib.request, json as _json, uuid
+ buf = io.BytesIO(); im.save(buf, format="PNG"); data = buf.getvalue()
+ boundary = "----wallco" + uuid.uuid4().hex
+ body = (f"--{boundary}\r\n"
+ f'Content-Disposition: form-data; name="content"; filename="{name}"\r\n'
+ f"Content-Type: image/png\r\n\r\n").encode() + data + \
+ f"\r\n--{boundary}--\r\n".encode()
+ req = urllib.request.Request(
+ "https://api.replicate.com/v1/files", data=body,
+ headers={"Authorization": "Bearer " + token,
+ "Content-Type": f"multipart/form-data; boundary={boundary}"})
+ r = _json.loads(urllib.request.urlopen(req, timeout=120).read())
+ url = (r.get("urls") or {}).get("get")
+ if not url:
+ raise RuntimeError("file upload: no url in " + str(r)[:160])
+ return url
+
+
def esrgan_tiled(img, token, target_long):
"""
Upscale to target_long honouring the 1448px ESRGAN input cap: at each x4
@@ -232,12 +270,10 @@ def outpaint_bleed(focal, bleed_px, top_prompt, bottom_prompt, token):
if not token:
return assemble_master(focal, bleed_px)
try:
- import base64, io as _io
- def b64(im, fmt="PNG"):
- b = _io.BytesIO(); im.save(b, format=fmt)
- return "data:image/png;base64," + base64.b64encode(b.getvalue()).decode()
+ img_url = _replicate_upload(canvas, token, "canvas.png")
+ mask_url = _replicate_upload(mask.convert("L"), token, "mask.png")
out = _replicate_run_latest("lucataco/sdxl-inpainting", {
- "image": b64(canvas), "mask": b64(mask),
+ "image": img_url, "mask": mask_url,
"prompt": f"{top_prompt} above, {bottom_prompt} below, soft natural "
"continuation of the scene, muted screen-print tones, no animals, no text",
"negative_prompt": "birds, butterflies, people, text, watermark, frame, border",
← 6e8a5ae loop tick8: regression check steady (no change)
·
back to Wallco Ai
·
render-live-rooms.py: PIL rooms for prod-only designs (pull a0b6407 →