{"record":{"id":"fb6cfbd678ae0d4f","repo":"mastra-ai/mastra","slug":"writemjpegavifile-frame-index-is-not-a-jpeg-fr","errorCode":null,"errorMessage":"writeMjpegAviFile: frame ${index} is not a JPEG frame (missing SOI marker)","messagePattern":"writeMjpegAviFile: frame (.+?) is not a JPEG frame \\(missing SOI marker\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/browser/recording/mjpeg-avi.ts","lineNumber":349,"sourceCode":"  p += 4;\n  buf.writeUInt32LE(0, p);\n  p += 4;\n  buf.writeUInt32LE(0, p);\n  p += 4;\n\n  if (p !== hdrlPayloadSize) {\n    throw new Error(`buildHdrl internal error: wrote ${p} bytes, expected ${hdrlPayloadSize}`);\n  }\n  return buf;\n}\n\nfunction paddedLength(n: number): number {\n  return n + (n & 1);\n}\n\nfunction assertJpegFrame(bytes: Uint8Array, index: number): void {\n  if (bytes.length < 2 || bytes[0] !== 0xff || bytes[1] !== 0xd8) {\n    throw new Error(`writeMjpegAviFile: frame ${index} is not a JPEG frame (missing SOI marker)`);\n  }\n}\n\nfunction assertU32(value: number, field: string): void {\n  if (!Number.isSafeInteger(value) || value < 0 || value > MAX_U32) {\n    throw new Error(`writeMjpegAviFile: ${field} exceeds 32-bit AVI limit (${value})`);\n  }\n}\n\n// ---------------------------------------------------------------------------\n// File I/O helpers\n// ---------------------------------------------------------------------------\n\nconst u32buf = Buffer.alloc(4);\n\nfunction writeFourCC(fd: number, fc: Buffer): void {\n  writeSync(fd, fc, 0, 4);\n}","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/browser/recording/mjpeg-avi.ts#L331-L367","documentation":"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.","triggerScenarios":"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[].","commonSituations":"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.","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"],"exampleFix":"// before\nconst shot = await page.screenshot({ type: 'png' }); // PNG bytes\nframes.push({ bytes: shot, ... });\n// after\nconst shot = await page.screenshot({ type: 'jpeg', quality: 80 });\nframes.push({ bytes: shot, ... });","handlingStrategy":"validation","validationCode":"function isJpeg(bytes) {\n  return bytes && bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8;\n}\nframes = rawFrames.filter(f => isJpeg(f.bytes));","typeGuard":"function isJpegFrame(f) {\n  return f.bytes instanceof Uint8Array && f.bytes.length >= 2\n    && f.bytes[0] === 0xff && f.bytes[1] === 0xd8;\n}","tryCatchPattern":"try {\n  writeMjpegAviFile(outPath, frames, opts);\n} catch (e) {\n  if (e.message.includes('missing SOI marker')) {\n    // re-encode offending frame as JPEG or drop it\n  } else throw e;\n}","preventionTips":["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"],"tags":["jpeg","avi","codec","frame-validation"],"backgroundTag":"invalid-jpeg-frame","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}