video_trim_output_requires_exact: keyframe cuts can't resize
FFmpeg stream copy cannot filter, and Sume's keyframe trim cannot take an output size or fps. Why it is refused, and the one-line fix.

POST /v1/video-trim returns video_trim_output_requires_exact when you send an output object (width, height or fps) together with precision: "keyframe". Keyframe mode is a stream copy, and a copy cannot change the picture. Switch to precision: "exact", which is the default, or drop output and conform the size later.
Why can't a stream copy resize?
The ffmpeg documentation describes streamcopy as copying packets with no decoding or encoding, fast and lossless, and states that applying filters is impossible because filters work on decoded frames. Scaling and frame-rate conversion are filters, so ffmpeg -c copy -vf scale=... cannot do what it says.
The same page explains the cut point: with -ss before the input, ffmpeg seeks to the closest seek point before the position, and in stream copy that extra segment is preserved. Sume's trim page describes the same effect: a keyframe cut may start a GOP early, so re-base against actual_start_seconds in the result.
| precision | How it cuts | output allowed | Start |
|---|---|---|---|
| exact (default) | Frame-accurate re-encode, libx264 and yuv420p | Yes: width and height 256 to 2160, fps 24, 25, 30 or 60 | At the requested start |
| keyframe | Stream copy | No: video_trim_output_requires_exact | May be a GOP early; read actual_start_seconds |
What are the two fixes?
Keep exact and send the output you wanted. The cut and the conform happen in one $0.02 job, since the Video trim page lists a flat public rate of $0.02 per job and says to confirm it in GET /v1/catalog. The other route is a keyframe cut with no output, then a Timeline render, whose output.width, output.height and output.fps set the final size.
Whichever you pick, the server compiles the ffmpeg arguments. Sending vf, codec, crf or similar fields gets ffmpeg_fields_rejected, so size changes only go through output.
How do I guard this in a client?
Build the body in one function and refuse the combination before the request leaves your code. The helper below runs as written and prints the accepted body, then the refusal it would have caused.
Remember the other trim limits when you test: a source up to 1,800 s, an output of 0.2 to 900 s, and exactly one of end or duration. Default mode is async, so poll GET /v1/jobs/:id/status and read GET /v1/jobs/:id/result for video_url.
import json
def trim_body(url, start, duration, precision="exact", size=None, fps=None):
body = {"video_url": url, "start": start, "duration": duration,
"precision": precision}
if size or fps:
if precision != "exact":
raise ValueError("video_trim_output_requires_exact")
body["output"] = {"width": size[0], "height": size[1], "fps": fps}
return body
url = "https://media.sume.com/artifacts/artf_demo/talk.mp4"
print(json.dumps(trim_body(url, 2, 8, "exact", (1280, 720), 30)))
try:
trim_body(url, 2, 8, "keyframe", (1280, 720), 30)
except ValueError as e:
print("refused:", e)When is keyframe mode still the right choice?
Use keyframe when you only need a fast, lossless cut and can accept a start that lands a little early, for example to pull a rough clip before review. The result tells you where the cut really began through actual_start_seconds, so a downstream step can re-base its own timings. For anything that must start on the exact frame, such as a transition point or a caption anchor, stay on exact.
A good habit is to pick the mode by the next step. If the next step is a Timeline render, the Timeline will conform size and rate anyway, so a keyframe trim followed by a render is often the cheaper pair. If the next step is delivery of the trimmed file itself, send exact with the output you need and skip the second job.
What do the cost and limits look like?
The Video trim page lists a flat $0.02 per job for both modes, with a source of up to 1,800 s and an output between 0.2 and 900 s. Because the price does not change with the mode, the choice is about accuracy and whether you need a new size or frame rate, not about cost.
Remember to send exactly one of end or duration, and an Idempotency-Key header on every submit, so a retry after a network error returns the original job and does not queue a second one.
Sources
Related posts
More in Developers
- video_url 400 on Sume: edit is supported only by Omni Flash 1.1
A video_url on any other model returns 400 on the Sume Video Router. Which models take a source video, and how to pick one for your edit.
- VideoObject for a product video: thumbnail, date and duration
Google's VideoObject needs name, thumbnailUrl and uploadDate. How to fill them and the duration from a Sume trim result and a video-frames still.
- Virtual try-on API: which Sume call returns an image, which a video
Need a try-on photo or a try-on clip? On Sume the two catalog try-on Formats return video; a still comes from the image API. The table, plus one call for each.
- Unit-test a Sume submit-and-poll loop in Vitest with fake replies
Test a Sume polling loop without spending credits: inject the sleep, replay queued then completed replies, and assert next_poll_after_seconds is honored.
Written by Sume