openvibers/openvibe.live Commits

AI-See: decode data-URI frames before saving moment thumbnails; deploy lock moves out of /tmp

OP OpenVibers committed 19 Sept 2026 · 244c5ad031 · 2 files, +11 −2

Moment frames from the 'look now' path arrive as data: URIs and were passed to
readFileSync, so every one failed with ENAMETOOLONG and the moment kept the
placeholder image. The deploy lock in /tmp could not be reopened by root once a
plain user had created it (fs.protected_regular); it now lives in the base dir.

Changes

ddiff --git a/deploy/scripts/deploy.sh b/deploy/scripts/deploy.sh
iindex 37a3cad5..ae68566e 100755
---- a/deploy/scripts/deploy.sh
++++ b/deploy/scripts/deploy.sh
@@@ -66,7 +66,8 @@ IDLE_MAX_SECONDS="${IDLE_MAX_SECONDS:-28800}"
KEEP_RELEASES="${KEEP_RELEASES:-5}"
NPM="${NPM:-npm}"
DRY_RUN="${DRY_RUN:-0}"
--LOCK_FILE="${LOCK_FILE:-/tmp/openvibe-live-deploy.lock}"
++# Not /tmp: fs.protected_regular stops root from reopening a lock a plain user created there.
++LOCK_FILE="${LOCK_FILE:-$BASE_DIR/.deploy.lock}"
say() { echo "[Deploy] $*"; }
die() { echo "[Deploy] ✗ $1" >&2; exit "${2:-1}"; }
ddiff --git a/server/ai/stream-memory-job.js b/server/ai/stream-memory-job.js
iindex adac3330..c038a19c 100644
---- a/server/ai/stream-memory-job.js
++++ b/server/ai/stream-memory-job.js
@@@ -51,7 +51,15 @@ async function _persistMomentFrame(image, streamId, offset) {
const dir = path.join(MOMENTS_DIR, String(streamId));
fs.mkdirSync(dir, { recursive: true });
const file = `${Math.max(0, Math.round(offset))}.jpg`;
-- const input = Buffer.isBuffer(image) ? image : fs.readFileSync(image);
++ // captureFrame may hand back a Buffer, a file path, or a data: URI (the AI viewers'
++ // "look now" path). A data URI is not a filename — decode it instead of open()ing it.
++ let input;
++ if (Buffer.isBuffer(image)) input = image;
++ else if (typeof image === 'string' && image.startsWith('data:')) {
++ const m = image.match(/^data:[^;,]+;base64,(.*)$/s);
++ if (!m) return null;
++ input = Buffer.from(m[1], 'base64');
++ } else input = fs.readFileSync(image);
await sharp(input).resize({ width: 320, withoutEnlargement: true }).jpeg({ quality: 72 }).toFile(path.join(dir, file));
return `/data/ai-moments/${streamId}/${file}`;
} catch (e) {