Timeline output.fps: why a 24 fps clip judders when you force 30
Leave output.fps unset and Timeline renders at the source rate. Force 30 on a 24 fps clip and frames repeat; the job reports output_fps_resamples_sources.

Leave output.fps out of your Timeline request and the render uses the frame rate of your sources. Set it to a value that differs from a source, for example 30 on a 24 fps clip, and the job repeats or drops a frame every few frames, which shows as judder on motion. The job reports that with the warning output_fps_resamples_sources, together with the rate the sources wanted. Allowed values are 24, 25, 30 and 60.
The rule for sources is spelled out in the docs: the longest video sources decide the rate, stills have no rate, and 30 applies only when no source carries a rate.
What the resampling does
Going from 24 to 30 fps means 6 extra frames per second must be invented by repeating. The ratio is 30 / 24 = 1.25, so on average every fourth frame is shown twice, which is uneven timing and what the eye reads as stutter. Going from 60 to 30 drops every second frame, which is regular and less visible, but still not a free change.
Mixed sources make the decision harder. If one clip is 24 fps and another 30 fps, the longest source sets the rate when you omit output.fps, and the other is resampled. The warning in the job result tells you which rate was wanted, so read warnings[] after the first render.
| Situation | Result |
|---|---|
| output.fps omitted, all sources 24 | Renders at 24 |
| output.fps omitted, no source has a rate (all stills) | Renders at 30 |
| output.fps 30, source 24 | Frames repeat; warning output_fps_resamples_sources |
| output.fps 24, source 60 | Frames dropped; same warning |
| output.fps 25 or 60 | Allowed values; same rule applies |
A render with the rate left alone
The sample below renders one clip over a 20-second voiceover with no output.fps. After the job completes, the result's warnings[] is empty if nothing needed resampling. Run the plan call first if you only want to see the cost; its billable_minutes for 20 seconds of audio is 1, so $0.10.
curl -X POST https://api.sume.com/v1/timeline-1.0/render \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fps-keep-001" \
-d '{
"audio": {
"url": "https://media.sume.com/artifacts/artf_demo/voice.wav",
"duration_seconds": 20
},
"video": [
{
"source_url": "https://media.sume.com/artifacts/artf_demo/clip24.mp4",
"start": 0,
"duration": 20
}
]
}'When you do need a fixed rate
Some destinations ask for one frame rate. YouTube's upload page, for example, lists different recommended bitrates for standard and high frame rates. If you must conform, do it once and do it deliberately: video trim can conform a clip with output: { fps: 30 } ahead of the Timeline job, and a conform there is the same re-encode you would otherwise get inside the render.
Note that the plan call does not predict these warnings; the docs say a plan cannot predict padded or looped sources and similar soft warnings. You find out from the result.
A short checklist
Probe the sources first with video inspect and read the frame rate in the probe block. If all sources agree, omit output.fps. If they differ, decide which rate wins and conform the others with video trim before rendering. Re-render only after changing the program, since each render is a new job.
Reading the warning
The warning is soft, which means the job still succeeds and you are billed as usual: it does not fail the render. That is why it is easy to miss. Make your pipeline read warnings[] from GET /v1/jobs/:id/result and log any entry whose code is output_fps_resamples_sources, along with the rate the sources wanted.
If you see it on a render where you did not set output.fps, one of your clips has a different rate than the longest source. Fix it by conforming that clip in advance, not by forcing the render to a third value.
Stills in the mix
A still has no frame rate, so a program made only of stills renders at 30 when you omit output.fps. If you mix stills with 24 fps clips, the clips decide the rate and the stills simply hold. That is the common case for ads that open on a product shot, and it means you rarely need to set the rate at all.
Sources
Related posts
More in Developers
- Transcribe audio with curl and jq: a Sume STT shell script
A 16-line bash script that submits audio to Sume STT, polls the job with curl, and prints every word with start and end times through jq. One cent per minute.
- unsupported_capability names sume/auto: fix a Sume ad clip request
A 400 unsupported_capability on sume/auto hides the resolved model but lists accepted values in supported. How to fix duration, resolution or audio.
- verifyWebhook in a fetch handler: four rules, 204 for unknown events
Use @sume-com/sdk verifyWebhook on the raw body, await it, treat false as 401 and answer unknown events with 204. A runnable handler for Workers, Deno and Node.
- Wan 3.0 API request cheat sheet: three modes, 2 to 30 seconds
Wan 3.0 on Sume: the request body for text, first/last frame and reference modes, the 480p/720p/1080p rates and the 2 to 30 second window, on one page.
Written by Sume