Summary
remotion_caption_burn fails on its Remotion path (the animated per-word caption renderer) with a compositor error, falling back to plain FFmpeg captions only if the caller passes force_ffmpeg: True. On a normal call it errors out:
Compositor error: No frame found at position 5546 for source <...>/clip_with_audio.mp4
The failure is deterministic and input-independent: I hit the identical position 5546 on two different source files — an 8.3 s clip muxed with -c:v copy, and a clean re-encode of the same clip (H.264, keyframe every frame, 205 frames). Because both are ~8.3 s but produce the same errant position, 5546 cannot be derived from the input duration — it points at the composition/tooling, not the video.
Operating system
Windows 11, Python 3.12, Remotion 4.0.484, Node 24
Pipeline
Caption burn-in (remotion_caption_burn), Remotion path (no force_ffmpeg)
Runtime / renderer
remotion
Steps to reproduce
-
Take any short clip (~8 s) with an audio track.
-
Transcribe it with transcriber to get word-level segments.
-
Burn captions without force_ffmpeg:
registry.get("remotion_caption_burn").execute({
"input_path": "clip_with_audio.mp4",
"output_path": "out.mp4",
"segments": segments,
"words_per_page": 3,
"font_size": 64,
})
Expected behavior
An MP4 with animated per-word captions burned in.
Actual behavior
The npx remotion render TalkingHead subprocess aborts with Compositor error: No frame found at position 5546. No output file is produced. force_ffmpeg: True works but renders static centred captions, losing the animated per-word highlight that is the point of the Remotion path.
Relevant logs or error output
Could not extract frame from compositor Error: Compositor error:
No frame found at position 5546 for source <tmp>/remotion-assets-dir/<hash>.mp4
(original source = http://localhost:3000/public/talking-head/clip_with_audio.mp4)
tools.base_tool.ToolCommandError: Command '[... 'remotion','render','TalkingHead',
'--props=public/demo-props/caption-burn-clip_with_audio.json',
'--width=1920','--height=1080','--fps=30','--frames=0-249', ...]' returned non-zero exit status 1
Investigation / prime suspect
TalkingHead is registered very differently from its siblings in remotion-composer/src/Root.tsx:
// Root.tsx (~L179)
<Composition
id="TalkingHead"
component={TalkingHead}
durationInFrames={30 * 300} // hardcoded 9000 frames (5 min), no calculateMetadata
fps={30}
width={1080} // declared PORTRAIT...
height={1920}
...
/>
Two things stand out versus Explainer / TitledVideo, which both derive duration from content via a calculateMetadata (e.g. durationInFrames: Math.ceil((lastEnd + 1) * 30)):
-
durationInFrames is a fixed 30 * 300 (9000 frames) rather than computed from the clip. The Python side does clamp the render with --frames=0-{ceil(duration*30)-1} (tools/video/remotion_caption_burn.py ~L291/L335), and that math is correct for an 8 s clip (0-249) — but <OffthreadVideo src={videoSrc}> in TalkingHead.tsx (~L320) has no explicit bounds, so the composition's own 9000-frame length may still drive a source seek beyond the clip.
-
Declared dimensions are portrait (1080×1920) but the tool overrides to landscape via --width/--height. Likely unrelated to the seek error, but it means the registered composition never matches the actual render and is worth aligning in the same pass.
Honesty caveat: I could not fully derive why the position is exactly 5546 — I confirmed it is not input-duration-derived (identical across two different-encoding files of the same length) and that the Python frame clamp itself is correct, but I did not trace 5546 to a specific line. The hardcoded 9000-frame duration with no calculateMetadata is the strongest candidate, not a proven root cause.
Suggested fix
Give TalkingHead a calculateMetadata that sets durationInFrames from the actual video (as Explainer/TitledVideo already do), and align its default width/height with how the tool renders it — or have the tool pass dimensions/duration the composition trusts. Happy to test a patch against this exact repro (Remotion 4.0.484).
Reminder for triage, matching a pattern in #431/#432: remotion_caption_burn reports available whenever Remotion is installed, so this only surfaces at call time.
Summary
remotion_caption_burnfails on its Remotion path (the animated per-word caption renderer) with a compositor error, falling back to plain FFmpeg captions only if the caller passesforce_ffmpeg: True. On a normal call it errors out:The failure is deterministic and input-independent: I hit the identical
position 5546on two different source files — an 8.3 s clip muxed with-c:v copy, and a clean re-encode of the same clip (H.264, keyframe every frame, 205 frames). Because both are ~8.3 s but produce the same errant position,5546cannot be derived from the input duration — it points at the composition/tooling, not the video.Operating system
Windows 11, Python 3.12, Remotion 4.0.484, Node 24
Pipeline
Caption burn-in (
remotion_caption_burn), Remotion path (noforce_ffmpeg)Runtime / renderer
remotion
Steps to reproduce
Take any short clip (~8 s) with an audio track.
Transcribe it with
transcriberto get word-levelsegments.Burn captions without
force_ffmpeg:Expected behavior
An MP4 with animated per-word captions burned in.
Actual behavior
The
npx remotion render TalkingHeadsubprocess aborts withCompositor error: No frame found at position 5546. No output file is produced.force_ffmpeg: Trueworks but renders static centred captions, losing the animated per-word highlight that is the point of the Remotion path.Relevant logs or error output
Investigation / prime suspect
TalkingHeadis registered very differently from its siblings inremotion-composer/src/Root.tsx:Two things stand out versus
Explainer/TitledVideo, which both derive duration from content via acalculateMetadata(e.g.durationInFrames: Math.ceil((lastEnd + 1) * 30)):durationInFramesis a fixed30 * 300(9000 frames) rather than computed from the clip. The Python side does clamp the render with--frames=0-{ceil(duration*30)-1}(tools/video/remotion_caption_burn.py~L291/L335), and that math is correct for an 8 s clip (0-249) — but<OffthreadVideo src={videoSrc}>inTalkingHead.tsx(~L320) has no explicit bounds, so the composition's own 9000-frame length may still drive a source seek beyond the clip.Declared dimensions are portrait (1080×1920) but the tool overrides to landscape via
--width/--height. Likely unrelated to the seek error, but it means the registered composition never matches the actual render and is worth aligning in the same pass.Honesty caveat: I could not fully derive why the position is exactly
5546— I confirmed it is not input-duration-derived (identical across two different-encoding files of the same length) and that the Python frame clamp itself is correct, but I did not trace5546to a specific line. The hardcoded 9000-frame duration with nocalculateMetadatais the strongest candidate, not a proven root cause.Suggested fix
Give
TalkingHeadacalculateMetadatathat setsdurationInFramesfrom the actual video (asExplainer/TitledVideoalready do), and align its default width/height with how the tool renders it — or have the tool pass dimensions/duration the composition trusts. Happy to test a patch against this exact repro (Remotion 4.0.484).Reminder for triage, matching a pattern in #431/#432:
remotion_caption_burnreportsavailablewhenever Remotion is installed, so this only surfaces at call time.