{"record":{"id":"cc88d7c105f77170","repo":"heygen-com/hyperframes","slug":"image-input-requires-a-png-output-got-extname","errorCode":null,"errorMessage":"Image input requires a .png output (got ${extname(outputPath)}). Use a video input for .webm/.mov.","messagePattern":"Image input requires a \\.png output \\(got (.+?)\\)\\. Use a video input for \\.webm/\\.mov\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/background-removal/pipeline.ts","lineNumber":241,"sourceCode":"  format: OutputFormat;\n  inputKind: \"video\" | \"image\";\n  bgFormat: OutputFormat | undefined;\n}\n\n/**\n * Resolve and validate the input/output combination before any I/O. Pure;\n * exported so unit tests can pin the error messages without spawning ffmpeg.\n */\nexport function resolveRenderTargets(\n  inputPath: string,\n  outputPath: string,\n  backgroundOutputPath?: string,\n): RenderTargets {\n  const format = inferOutputFormat(outputPath);\n  const inputKind = inferInputKind(inputPath);\n\n  if (inputKind === \"image\" && format !== \"png\") {\n    throw new Error(\n      `Image input requires a .png output (got ${extname(outputPath)}). Use a video input for .webm/.mov.`,\n    );\n  }\n  if (inputKind === \"video\" && format === \"png\") {\n    throw new Error(\n      `Video input requires a .webm or .mov output (got .png). Use an image input for .png.`,\n    );\n  }\n\n  let bgFormat: OutputFormat | undefined;\n  if (backgroundOutputPath) {\n    if (inputKind === \"image\") {\n      throw new Error(\n        \"--background-output is not supported for image inputs. Use a video input (mp4/mov/webm) to produce both a cutout and a background plate.\",\n      );\n    }\n    bgFormat = inferOutputFormat(backgroundOutputPath);\n    if (bgFormat === \"png\") {","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/background-removal/pipeline.ts#L223-L259","documentation":"Thrown by resolveRenderTargets() when an image input (jpg/png/webp) is paired with a non-.png output. The pipeline only produces a single RGBA still for image inputs (encoded as PNG with alpha); video-container outputs like .webm/.mov require a frame sequence, which a single image cannot supply. This guard runs before any ffmpeg I/O.","triggerScenarios":"Calling render({ inputPath: 'subject.jpg', outputPath: 'cutout.webm' }) or any image input with a .webm/.mov output. resolveRenderTargets() calls inferInputKind then checks the (image, format!==png) combination.","commonSituations":"User records a static product photo and asks for a transparent .webm; copy-paste of a video-oriented output path for an image job; scripted pipeline that reuses one output template for both image and video inputs.","solutions":["Change the output path to .png when the input is a single image: `out.png`.","If you need a .webm/.mov result, supply a video input (mp4/mov/webm/mkv/avi) instead of a still.","In a shared pipeline, branch on input kind before choosing the output extension."],"exampleFix":"// before\nawait render({ inputPath: 'product.jpg', outputPath: 'product.webm' });\n// after\nawait render({ inputPath: 'product.jpg', outputPath: 'product.png' });","handlingStrategy":"validation","validationCode":"import { extname } from 'node:path';\nfunction pickOutputExtForInput(input: string, desired: string): string {\n  const inExt = extname(input).toLowerCase();\n  const isImage = ['.jpg','.jpeg','.png','.webp'].includes(inExt);\n  if (isImage) return '.png';\n  return desired;\n}\nconst outputPath = replaceExt(options.outputPath, pickOutputExtForInput(options.inputPath, '.webm'));\nawait render({ ...options, outputPath });","typeGuard":"function imageInputNeedsPng(inputPath: string, outputPath: string): boolean {\n  const inExt = extname(inputPath).toLowerCase();\n  const isImage = ['.jpg','.jpeg','.png','.webp'].includes(inExt);\n  const outExt = extname(outputPath).toLowerCase();\n  return isImage && outExt !== '.png';\n}","tryCatchPattern":null,"preventionTips":["Choose the output extension based on input kind: image → .png, video → .webm/.mov.","In shared pipelines, branch on inferInputKind before assigning outputPath.","Write a unit test that pins the (image, non-png) rejection like the library's own tests do."],"tags":["validation","format-mismatch","background-removal","pre-io"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}