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

  1. Ensure the capture API encodes JPEG (e.g. screenshot({ type: 'jpeg', quality })) before pushing frames
  2. Check bytes[0] === 0xff && bytes[1] === 0xd8 at capture time and drop/re-encode bad frames
  3. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/fb6cfbd678ae0d4f. Report an issue: GitHub.