{"record":{"id":"642499ce4a7c7327","repo":"heygen-com/hyperframes","slug":"unsupported-input-ext-use-a-video-mp4-mov-we","errorCode":null,"errorMessage":"Unsupported input: ${ext}. Use a video (mp4/mov/webm/mkv/avi) or image (jpg/png/webp).","messagePattern":"Unsupported input: (.+?)\\. Use a video \\(mp4/mov/webm/mkv/avi\\) or image \\(jpg/png/webp\\)\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/background-removal/pipeline.ts","lineNumber":100,"sourceCode":"  fps: number;\n  frameCount: number;\n}\n\nexport function inferOutputFormat(outputPath: string): OutputFormat {\n  const ext = extname(outputPath).toLowerCase();\n  if (ext === \".webm\") return \"webm\";\n  if (ext === \".mov\") return \"mov\";\n  if (ext === \".png\") return \"png\";\n  throw new Error(\n    `Unsupported output extension: ${ext}. Use .webm (VP9 alpha), .mov (ProRes 4444), or .png.`,\n  );\n}\n\nexport function inferInputKind(inputPath: string): \"video\" | \"image\" {\n  const ext = extname(inputPath).toLowerCase();\n  if (VIDEO_EXTENSIONS.has(ext)) return \"video\";\n  if (IMAGE_EXTENSIONS.has(ext)) return \"image\";\n  throw new Error(\n    `Unsupported input: ${ext}. Use a video (mp4/mov/webm/mkv/avi) or image (jpg/png/webp).`,\n  );\n}\n\ninterface EngineMetadata {\n  width: number;\n  height: number;\n  fps: number;\n  durationSeconds: number;\n}\n\nasync function probeMedia(inputPath: string): Promise<MediaInfo> {\n  const isImage = inferInputKind(inputPath) === \"image\";\n  const engine = (await import(\"@hyperframes/engine\")) as {\n    extractMediaMetadata: (path: string) => Promise<EngineMetadata>;\n  };\n  const meta = await engine.extractMediaMetadata(inputPath);\n","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/background-removal/pipeline.ts#L82-L118","documentation":"Thrown by inferInputKind() when the input file's extension matches neither VIDEO_EXTENSIONS (.mp4/.mov/.webm/.mkv/.avi) nor IMAGE_EXTENSIONS (.jpg/.jpeg/.png/.webp). The background-removal pipeline only accepts raster video or image inputs because it feeds raw RGB24 frames to an ONNX inference session. Any other extension (e.g. .gif, .tiff, .bmp, no extension, or a typo) is rejected before any ffmpeg probe runs.","triggerScenarios":"Calling render() or resolveRenderTargets() with an inputPath whose extname is outside the two extension sets. Examples: input '/clip.gif', input 'photo.tiff', input 'frame' (no ext), or a typo like 'video.mp4' written as 'video.mp4 ' with a trailing space.","commonSituations":"User passes a .gif or .tiff thinking it is a supported image; a glob or shell expansion produced an unexpected file (e.g. a .txt sidecar); the path lost its extension during a rename; a case mismatch where the file is actually .MP4 but extname returns it correctly after toLowerCase (works) — real failures are unsupported formats or missing extensions.","solutions":["Convert the input to a supported format: video → .mp4/.mov/.webm, still → .png/.jpg/.webp (ffmpeg or an image tool).","Check the file extension is spelled correctly and present: run `ls -la <inputPath>` and confirm the ext.","If you genuinely need .gif, extract frames first: `ffmpeg -i in.gif -vsync 0 frame%04d.png` then pass a frame, or convert to mp4."],"exampleFix":"// before\nawait render({ inputPath: 'clip.gif', outputPath: 'out.webm' });\n// after (convert first)\n// $ ffmpeg -i clip.gif -movflags faststart clip.mp4\nawait render({ inputPath: 'clip.mp4', outputPath: 'out.webm' });","handlingStrategy":"validation","validationCode":"import { extname } from 'node:path';\nconst VIDEO = new Set(['.mp4','.mov','.webm','.mkv','.avi']);\nconst IMAGE = new Set(['.jpg','.jpeg','.png','.webp']);\nfunction assertSupportedInput(p: string) {\n  const ext = extname(p).toLowerCase();\n  if (!VIDEO.has(ext) && !IMAGE.has(ext)) {\n    throw new Error(`Unsupported input extension ${ext}. Convert to mp4/mov/webm/mkv/avi (video) or jpg/png/webp (image).`);\n  }\n}\nassertSupportedInput(options.inputPath);\nawait render(options);","typeGuard":"import { extname } from 'node:path';\nfunction isSupportedInput(p: string): boolean {\n  const ext = extname(p).toLowerCase();\n  return new Set(['.mp4','.mov','.webm','.mkv','.avi','.jpg','.jpeg','.png','.webp']).has(ext);\n}","tryCatchPattern":null,"preventionTips":["Validate the input extension with inferInputKind (or a mirror of it) before calling render().","When building a CLI wrapper, surface the supported extension list in --help.","Reject paths with no extension early in your own argument parsing."],"tags":["validation","file-format","background-removal","pre-io"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}