heygen-com/hyperframes · error

No frames produced from ${inputPath}. Decoder stderr: ${deco

Error message

No frames produced from ${inputPath}. Decoder stderr:
${decoder.getStderr().slice(-400)}

What it means

Thrown at the end of runPipeline() after the ffmpeg decoder process finishes and produces zero frames (processed === 0). The pipeline decodes the input via `ffmpeg -i <input> -f rawvideo -pix_fmt rgb24 -` and iterates the stdout stream; if nothing came through, the decoder's stderr tail is appended so the root cause (codec unsupported, corrupt input, wrong path) is visible rather than an opaque 'no output'.

Source

Thrown at packages/cli/src/background-removal/pipeline.ts:462

        total,
        avgMsPerFrame: recentSum / recentCount,
      });
    }
  } catch (err) {
    decoder.proc.kill("SIGKILL");
    fg.proc.kill("SIGKILL");
    bg?.proc.kill("SIGKILL");
    throw err;
  }

  fg.proc.stdin!.end();
  bg?.proc.stdin!.end();
  const exits: Promise<void>[] = [decoder.exit, fg.exit];
  if (bg) exits.push(bg.exit);
  await Promise.all(exits);

  if (processed === 0) {
    throw new Error(
      `No frames produced from ${inputPath}. Decoder stderr:\n${decoder.getStderr().slice(-400)}`,
    );
  }

  return processed;
}

export function waitForExit(
  proc: ReturnType<typeof spawn>,
  label: string,
  getStderr: () => string,
): Promise<void> {
  return new Promise<void>((resolve, reject) => {
    proc.on("error", reject);
    // Per Node docs the exit callback is (code, signal): on a normal exit
    // `code` is the numeric exit status and `signal` is null; on a
    // signal-killed exit `code` is null and `signal` is the signal name.
    // Treating null-code as success would silently report SIGTERM/SIGKILL

View on GitHub (pinned to c2996c8626)

Solutions

  1. Read the decoder stderr tail in the error message — it usually names the real cause (e.g. 'Unknown codec', 'Invalid data found', 'No such file').
  2. Test the input independently: `ffmpeg -i <input> -f null -` and fix based on the reported error.
  3. If the codec is unsupported, re-encode to H.264/AAC mp4: `ffmpeg -i in.mkv -c:v libx264 -c:a aac out.mp4`.
  4. If the file is corrupt, re-download or re-export the source.

Example fix

# before — input is HEVC but ffmpeg lacks libx265
$ hyperframes bg-remove -i hevc_clip.mp4 ...
# re-encode to a broadly supported codec
$ ffmpeg -i hevc_clip.mp4 -c:v libx264 -preset fast -crf 18 h264_clip.mp4
$ hyperframes bg-remove -i h264_clip.mp4 ...
Defensive patterns

Strategy: try-catch

Validate before calling

import { execFileSync } from 'node:child_process';
function inputDecodes(inputPath: string): boolean {
  try {
    execFileSync('ffmpeg', ['-i', inputPath, '-f', 'null', '-'], { stdio: 'ignore', timeout: 30_000 });
    return true;
  } catch {
    return false;
  }
}
if (!inputDecodes(options.inputPath)) {
  throw new Error(`Input cannot be decoded by ffmpeg: ${options.inputPath}`);
}

Try / catch

try {
  await render(options);
} catch (err) {
  const msg = (err as Error).message;
  if (/No frames produced/.test(msg)) {
    console.error('Decoder produced no frames. Check the stderr tail:\n' + msg);
    // re-encode the input, then retry
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling render() with a path that ffmpeg cannot decode: a corrupted mp4, a file with an unsupported codec (e.g. HEVC on a minimal ffmpeg build), a zero-byte file, a wrong path that ffmpeg still opens but cannot read, or a container/codec mismatch.

Common situations: Truncated/corrupted download passed as input; a minimal ffmpeg build without libx265 decoding a HEVC clip; the file path is a directory or pipe instead of a regular file; mismatched extension vs actual container.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/b1d39d9e9b7f60e4. Report an issue: GitHub.