← 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
M lib/composition-detector.jsM lib/subject-detector.js
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 →