{"record":{"id":"76cf76f1d1a4ba76","repo":"heygen-com/hyperframes","slug":"chunkencoder-lockgopforchunkconcat-true-requires","errorCode":null,"errorMessage":"[chunkEncoder] lockGopForChunkConcat=true requires a positive integer gopSize (received ${String(options.gopSize)})","messagePattern":"\\[chunkEncoder\\] lockGopForChunkConcat=true requires a positive integer gopSize \\(received (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/engine/src/services/chunkEncoder.ts","lineNumber":254,"sourceCode":"      const encoderName = codec === \"h264\" ? \"libx264\" : \"libx265\";\n      args.push(\"-c:v\", encoderName, \"-preset\", preset);\n      if (bitrate) args.push(\"-b:v\", bitrate);\n      else args.push(\"-crf\", String(quality));\n\n      // Closed-GOP / forced-keyframe args so an external orchestrator can\n      // ffmpeg-concat chunk files with `-c copy`. Without these, libx264 /\n      // libx265 emit open-GOP frames with mid-chunk scenecut keyframes; the\n      // first frame of each chunk isn't an independently-decodable IDR and\n      // concat-copy playback freezes at chunk seams on some decoders.\n      const lockGop = options.lockGopForChunkConcat === true;\n      let gop = 0;\n      if (lockGop) {\n        if (\n          typeof options.gopSize !== \"number\" ||\n          !Number.isFinite(options.gopSize) ||\n          options.gopSize <= 0\n        ) {\n          throw new Error(\n            `[chunkEncoder] lockGopForChunkConcat=true requires a positive integer gopSize (received ${String(options.gopSize)})`,\n          );\n        }\n        gop = Math.floor(options.gopSize);\n        args.push(\n          \"-g\",\n          String(gop),\n          \"-keyint_min\",\n          String(gop),\n          \"-sc_threshold\",\n          \"0\",\n          \"-force_key_frames\",\n          `expr:eq(mod(n,${gop}),0)`,\n        );\n      }\n\n      // Disable B-frames. Standard h264 with B-frames produces negative DTS\n      // at the start of the stream (the first B-frame's decode order is","sourceCodeStart":236,"sourceCodeEnd":272,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/engine/src/services/chunkEncoder.ts#L236-L272","documentation":"Thrown by the H.264/H.265 software encoder branch in chunkEncoder when lockGopForChunkConcat is true but gopSize is not a positive finite number. Closed-GOP encoding forces fixed keyframe intervals (-g, -keyint_min, -sc_threshold 0, -force_key_frames) so chunk files can be concatenated with ffmpeg -c copy without frozen seams. Without a valid GOP size, these flags cannot be set and concat-copy playback would break.","triggerScenarios":"encodeChunk() (or equivalent) is called with options.lockGopForChunkConcat === true and options.gopSize that is undefined, NaN, Infinity, zero, negative, or a non-number. The validation checks typeof !== 'number', !Number.isFinite(), and <= 0.","commonSituations":"lockGopForChunkConcat was set to true by default in an orchestrator config but gopSize was never configured. A config merge overwrote gopSize with undefined. A CLI flag parser produced a string instead of a number. gopSize was set to 0 thinking it means 'auto'.","solutions":["Set gopSize to the frame rate (for 1-second GOPs) or 2x frame rate: e.g., gopSize: 60 for 60fps.","If you don't need concat-copy compatibility, set lockGopForChunkConcat: false instead.","Validate config at load time: if lockGopForChunkConcat is true, assert gopSize is a positive integer before encoding.","Check that your config parser coerces gopSize to a number (parseInt or Number()) rather than passing a string."],"exampleFix":"// before\nencodeChunk(input, output, {\n  codec: 'h264',\n  lockGopForChunkConcat: true,\n  // gopSize missing!\n});\n\n// after\nencodeChunk(input, output, {\n  codec: 'h264',\n  lockGopForChunkConcat: true,\n  gopSize: frameRate * 2, // closed GOP at 2-second intervals\n});","handlingStrategy":"validation","validationCode":"function validateEncoderOptions(options: EncodeChunkOptions): void {\n  if (options.lockGopForChunkConcat === true) {\n    if (typeof options.gopSize !== 'number' || !Number.isFinite(options.gopSize) || options.gopSize <= 0) {\n      throw new Error('lockGopForChunkConcat requires gopSize to be a positive integer');\n    }\n  }\n}\n\nvalidateEncoderOptions(options);","typeGuard":"function hasValidGopSize(options: unknown): options is { gopSize: number; lockGopForChunkConcat: true } {\n  if (typeof options !== 'object' || options === null) return false;\n  const o = options as Record<string, unknown>;\n  return o.lockGopForChunkConcat === true\n    && typeof o.gopSize === 'number'\n    && Number.isFinite(o.gopSize)\n    && o.gopSize > 0;\n}","tryCatchPattern":null,"preventionTips":["Always pair lockGopForChunkConcat: true with a positive integer gopSize in config templates.","Validate config at load time rather than at encode time to fail fast.","Use a shared config schema (e.g., zod) to enforce the dependency between lockGopForChunkConcat and gopSize."],"tags":["encoder","h264","h265","gop","config","validation","ffmpeg"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}