Video frames caps at 24 stills: a 15 s hook needs fps 1.6, not 2
Video frames returns at most 24 stills and fps up to 2. At 2 fps only 12 s are covered, so a 15 s hook needs fps 1.6; the Python below checks any length.

Video frames returns at most 24 stills per call, and fps is capped at 2. The two limits meet at 12 seconds: at 2 fps a 12-second clip yields exactly 24 frames, and a longer clip is cut off at 24. A 15-second hook needs fps 1.6, since 24 / 15 = 1.6.
Limits
From the Sume docs.
| Field | Limit |
|---|---|
at[] | 1 to 24 values, each 0 <= t < duration |
fps | 0 < fps <= 2, expanded to mid-bin samples (0.5/fps, 1.5/fps, ...), 24 frames max |
| Source length | 300 s or less |
max_edge | 16 to 2160, optional |
| Price | Modal compute, not a fixed fee |
Choose fps for a length
Send only one of at[] or fps. This function picks the highest allowed rate for a clip so every second is covered, and shows the first sample times.
def pick_fps(seconds):
if seconds <= 0 or seconds > 300:
raise ValueError("source must be 0-300 s")
return min(2.0, 24 / seconds)
for s in (6, 12, 15, 30):
f = pick_fps(s)
n = min(24, round(s * f))
first = [round((i + 0.5) / f, 2) for i in range(3)]
print(s, "s -> fps", round(f, 3), n, "frames", first)Why mid-bin samples matter
Mid-bin sampling takes a frame from the middle of each time slot, not at 0 s. At 1.6 fps the first three stills fall at 0.31 s, 0.94 s and 1.56 s. A hook that cuts at second 1 is therefore seen in the second still. If you need a specific instant, use at[] and name the seconds yourself.
For a 30-second clip the cap sets fps at 0.8, about one still every 1.25 seconds. Video inspect samples eight mid-bin stills instead, with a default max_edge of 768, plus a probe and optional speech to text.
Longer clips
Video frames accepts a source up to 300 seconds. The 24-still cap is a hard stop, so a 300-second clip gives one still per 12.5 seconds at best (300 / 24). If you need finer coverage of part of a long clip, trim that part first for $0.02 and then extract frames from the trim, or name exact instants with at[].
Frames are billed by their Modal compute with a hold taken at submit, and the bill never exceeds the hold, so the cost is not a flat fee and is not shown here. Frames are returned as durable media.sume.com images at source size unless you set max_edge.
When to use at instead
fps samples evenly. If you care about specific moments, such as the first frame after a cut, use at[] with explicit times. Both forms count against the same 24-frame ceiling, and a time at or past the clip's duration fails with frame_time_out_of_range, so probe the length first with video inspect.
For a Short check you usually want the first second, the middle and the end: three values, nowhere near the cap.
A review routine
For a quick hook review, extract frames at 0.25, 0.75, 1.5, 3, 6, 9, 12 and 14.5 seconds with at[] on a 15-second clip: eight values, well inside the 24 cap. For a pass over the whole clip with no choice of instants, use fps 1.6, which gives 24 stills. The frames come back as durable images, so you can attach them to a review or pass them to an image-reading model.
If the clip is not yet hosted on media.sume.com, import it first with POST /v1/media-imports; the frames route does not fetch from the open internet.
Sources
Related posts
More in Developers
- One env line picks the video model: $1.25 vs $5.78 per 10 s clip
Read the Sume video model id from one env var. For a 10 s 720p 9:16 clip, gemini-omni-flash-1.1 bills $1.25 and seedance-2.5 bills $5.78.
- Clip-type route table in Python: Sora use cases to Sume models
A 30-line Python route table maps ads, demos and teasers to a Sume model, size and length, refuses a length the model cannot make, and prints the dollar cost.
- Video trim end past the clip: trim_clamped_to_source and a short ad
If end is beyond the source, Sume video-trim clamps it and warns trim_clamped_to_source. Ask for 0-20 s of a 15 s clip and you get 15 s, which can break a spot.
- Video upscale hold: omit duration_seconds and Sume reserves 5 s
Sume video upscale reserves from duration_seconds, or 5 seconds when you omit it. Hold table for 5, 15 and 30 seconds at $0.009 per second and the 402 case.
Written by Sume