mastra-ai/mastra · error · Error
writeMjpegAviFile: frame ${index} is not a JPEG frame (missi
Error message
writeMjpegAviFile: frame ${index} is not a JPEG frame (missing SOI marker) What it means
assertJpegFrame requires every frame to begin with the JPEG SOI marker (0xFF 0xD8), since the AVI 'MJPG' codec expects raw JPEG data per frame. A frame shorter than 2 bytes or lacking SOI is not a valid JPEG, so muxing is aborted with the offending frame index.
Source
Thrown at packages/core/src/browser/recording/mjpeg-avi.ts:349
p += 4;
buf.writeUInt32LE(0, p);
p += 4;
buf.writeUInt32LE(0, p);
p += 4;
if (p !== hdrlPayloadSize) {
throw new Error(`buildHdrl internal error: wrote ${p} bytes, expected ${hdrlPayloadSize}`);
}
return buf;
}
function paddedLength(n: number): number {
return n + (n & 1);
}
function assertJpegFrame(bytes: Uint8Array, index: number): void {
if (bytes.length < 2 || bytes[0] !== 0xff || bytes[1] !== 0xd8) {
throw new Error(`writeMjpegAviFile: frame ${index} is not a JPEG frame (missing SOI marker)`);
}
}
function assertU32(value: number, field: string): void {
if (!Number.isSafeInteger(value) || value < 0 || value > MAX_U32) {
throw new Error(`writeMjpegAviFile: ${field} exceeds 32-bit AVI limit (${value})`);
}
}
// ---------------------------------------------------------------------------
// File I/O helpers
// ---------------------------------------------------------------------------
const u32buf = Buffer.alloc(4);
function writeFourCC(fd: number, fc: Buffer): void {
writeSync(fd, fc, 0, 4);
}View on GitHub (pinned to 75dd419e61)
Solutions
- Ensure the capture API encodes JPEG (e.g. screenshot({ type: 'jpeg', quality })) before pushing frames
- Check bytes[0] === 0xff && bytes[1] === 0xd8 at capture time and drop/re-encode bad frames
- Never pass PNG/WebP/Raw RGBA buffers to MjpegFrame.bytes; convert them first
Example fix
// before
const shot = await page.screenshot({ type: 'png' }); // PNG bytes
frames.push({ bytes: shot, ... });
// after
const shot = await page.screenshot({ type: 'jpeg', quality: 80 });
frames.push({ bytes: shot, ... }); Defensive patterns
Strategy: validation
Validate before calling
function isJpeg(bytes) {
return bytes && bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8;
}
frames = rawFrames.filter(f => isJpeg(f.bytes)); Type guard
function isJpegFrame(f) {
return f.bytes instanceof Uint8Array && f.bytes.length >= 2
&& f.bytes[0] === 0xff && f.bytes[1] === 0xd8;
} Try / catch
try {
writeMjpegAviFile(outPath, frames, opts);
} catch (e) {
if (e.message.includes('missing SOI marker')) {
// re-encode offending frame as JPEG or drop it
} else throw e;
} Prevention
- Capture with type: 'jpeg' in your screenshot API
- Validate SOI bytes when pushing frames into the array
- Never feed PNG/WebP/Raw RGBA buffers to an MJPEG muxer
When it happens
Trigger: Passing frames whose bytes are PNG/WebP/raw RGBA instead of JPEG; a truncated or empty Uint8Array from a failed capture; mixing codecs when pushing frames into MjpegFrame[].
Common situations: Configuring browser screenshot capture as PNG (default in many drivers) and feeding those bytes straight into the MJPEG muxer; a frame cut short by a failed network/pipe read; reusing a buffer after a partial write.
Related errors
- fourcc must be 4 ASCII characters, got "${s}"
- writeMjpegAviFile: at least one frame is required
- writeMjpegAviFile: invalid dimensions ${opts.width}x${opts.h
- writeMjpegAviFile: dimensions exceed AVI header bounds: ${op
- buildHdrl internal error: wrote ${p} bytes, expected ${hdrlP
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/fb6cfbd678ae0d4f.
Report an issue: GitHub.