[object Object]

← back to Wallco Ai

detectors: handle Nano Banana C2PA JPEG (b64 magic /9j/, path = short+exists)

46bc4160b15125e3628ea10427fdaa1ad6ea0da1 · 2026-06-03 08:26:03 -0700 · Steve

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 46bc4160b15125e3628ea10427fdaa1ad6ea0da1
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jun 3 08:26:03 2026 -0700

    detectors: handle Nano Banana C2PA JPEG (b64 magic /9j/, path = short+exists)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 lib/composition-detector.js | 11 +++++++----
 lib/subject-detector.js     |  7 +++++--
 2 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/lib/composition-detector.js b/lib/composition-detector.js
index c89c7ad..122cfbf 100644
--- a/lib/composition-detector.js
+++ b/lib/composition-detector.js
@@ -86,14 +86,17 @@ function readKey() {
 function toBase64(input) {
   if (Buffer.isBuffer(input)) return { b64: input.toString('base64'), mime: 'image/png' };
   if (typeof input !== 'string') throw new Error('composition-detector: input must be path|b64|buffer');
-  // Heuristic: starts with '/' or contains '.' near end → treat as path
-  if (input.startsWith('/') || /\.(png|jpg|jpeg)$/i.test(input)) {
+  // A real path is short and exists on disk. A base64 image is long — and a
+  // JPEG base64 even starts with "/9j/", which naively looks like an abs path
+  // (this broke Nano Banana 3.1 / gemini-3.x, which return C2PA-signed JPEG).
+  // So only treat as a path when it's a plausible length AND actually exists.
+  if (input.length < 4096 && fs.existsSync(input)) {
     const bytes = fs.readFileSync(input);
     const mime  = /\.png$/i.test(input) ? 'image/png' : 'image/jpeg';
     return { b64: bytes.toString('base64'), mime };
   }
-  // Otherwise assume it's already base64
-  return { b64: input, mime: 'image/png' };
+  // Otherwise it's already base64 — detect JPEG vs PNG from the b64 magic.
+  return { b64: input, mime: /^\/9j\//.test(input) ? 'image/jpeg' : 'image/png' };
 }
 
 async function analyzeComposition(input, opts = {}) {
diff --git a/lib/subject-detector.js b/lib/subject-detector.js
index 223bf70..7c3f193 100644
--- a/lib/subject-detector.js
+++ b/lib/subject-detector.js
@@ -47,12 +47,15 @@ function readKey() {
 function toBase64(input) {
   if (Buffer.isBuffer(input)) return { b64: input.toString('base64'), mime: 'image/png' };
   if (typeof input !== 'string') throw new Error('subject-detector: input must be path|b64|buffer');
-  if (input.startsWith('/') || /\.(png|jpg|jpeg)$/i.test(input)) {
+  // Only a short, on-disk string is a path. A JPEG base64 starts with "/9j/"
+  // (looks like an abs path) — Nano Banana 3.1 returns C2PA-signed JPEG, which
+  // the naive startsWith('/') check mis-read as a path. Length+exists guard fixes it.
+  if (input.length < 4096 && fs.existsSync(input)) {
     const bytes = fs.readFileSync(input);
     const mime  = /\.png$/i.test(input) ? 'image/png' : 'image/jpeg';
     return { b64: bytes.toString('base64'), mime };
   }
-  return { b64: input, mime: 'image/png' };
+  return { b64: input, mime: /^\/9j\//.test(input) ? 'image/jpeg' : 'image/png' };
 }
 
 // Best-effort PRIMARY-SUBJECT extraction. Returns up to N noun phrases,

← 8dad76a build-tif: Real-ESRGAN mural upscale stage (DTD verdict A) —  ·  back to Wallco Ai  ·  dogs/luxe gen: GEMINI_IMAGE_MODEL env override (re-roll on a 547c9df →