openvibers/openvibe.live Commits
AI-See: decode data-URI frames before saving moment thumbnails; deploy lock moves out of /tmp
Changes
@@@ -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}"; }@@@ -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) {
@@ -66,7 +66,8 @@ IDLE_MAX_SECONDS="${IDLE_MAX_SECONDS:-28800}"
@@ -66,7 +66,8 @@ IDLE_MAX_SECONDS="${IDLE_MAX_SECONDS:-28800}"
KEEP_RELEASES="${KEEP_RELEASES:-5}"
KEEP_RELEASES="${KEEP_RELEASES:-5}"
NPM="${NPM:-npm}"
NPM="${NPM:-npm}"
DRY_RUN="${DRY_RUN:-0}"
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] $*"; }
say() { echo "[Deploy] $*"; }
die() { echo "[Deploy] ✗ $1" >&2; exit "${2:-1}"; }
die() { echo "[Deploy] ✗ $1" >&2; exit "${2:-1}"; }
@@ -51,7 +51,15 @@ async function _persistMomentFrame(image, streamId, offset) {
@@ -51,7 +51,15 @@ async function _persistMomentFrame(image, streamId, offset) {
const dir = path.join(MOMENTS_DIR, String(streamId));
const dir = path.join(MOMENTS_DIR, String(streamId));
fs.mkdirSync(dir, { recursive: true });
fs.mkdirSync(dir, { recursive: true });
const file = `${Math.max(0, Math.round(offset))}.jpg`;
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));
await sharp(input).resize({ width: 320, withoutEnlargement: true }).jpeg({ quality: 72 }).toFile(path.join(dir, file));
return `/data/ai-moments/${streamId}/${file}`;
return `/data/ai-moments/${streamId}/${file}`;
} catch (e) {
} catch (e) {