Last frame of an Omni clip: fixing frame_time_out_of_range
Asking Sume video-frames for t equal to the clip length fails with frame_time_out_of_range. Take the last frame at duration minus a frame, in Python.

To get the last frame of an Omni clip on Sume, call POST /v1/video-frames with at set slightly below the clip length, for example 9.9 for a 10-second clip. If you send at: [10], the worker fails with frame_time_out_of_range, because each time must satisfy 0 <= t < duration. The end instant itself is outside the range.
Why you want that frame
Google's Gemini Omni 1.1 Flash scene extension reads up to 10 seconds of prior context and extends in 10-second steps to 40 seconds (Google, read 2026-10-05). The catalog model on Sume has no extend mode. The by-hand version is to take the last frame of clip N and send it as image_url, the first frame of clip N+1 (Sume docs: Video Router, read 2026-10-05).
What the API says
Those rules come from Sume docs: Video frames, read 2026-10-05. The generated clip is a Sume artifact, so its URL from GET /v1/jobs/{id}/result is already on the media host and needs no import.
at[]takes 1 to 24 values, each at least 0 and strictly less than the probed duration.- The source must be a
media.sume.comartifact of your workspace, and at most 300 seconds. - A submit always returns 202. Poll
GET /v1/video-frames/:id; whenresource_statusisready,frames[]holds{t, url, width, height}. - The result carries
source_duration_seconds, the duration the worker probed. The failure message also gives the probed duration. - Frames keep the source frame size unless you send
max_edge.
Python
The code asks for 0.1 seconds before the requested duration. A provider can return a clip a few hundredths longer or shorter than the request, so if you see the error, retry with the probed duration minus 0.1.
import os, time, requests
API = "https://api.sume.com"
AUTH = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
def last_frame(video_url, duration):
body = {"video_url": video_url, "at": [round(duration - 0.1, 2)],
"format": "png"}
r = requests.post(f"{API}/v1/video-frames", json=body,
headers={**AUTH, "Idempotency-Key": f"lf-{video_url[-24:]}"})
r.raise_for_status()
rid = r.json()["request_id"]
while True:
s = requests.get(f"{API}/v1/video-frames/{rid}", headers=AUTH).json()
obj = s.get("video_frames", s)
if obj.get("resource_status") == "ready":
return obj["frames"][0]["url"]
if obj.get("resource_status") == "failed":
raise RuntimeError(obj)
time.sleep(3)
# url = result["artifacts"][0]["url"] from GET /v1/jobs/{id}/result
# print(last_frame(url, 10))Then chain
Send the returned image URL as image_url for the next clip, with a prompt that continues the action. Add end_image_url only if you also have a target last frame; end_image_url needs image_url too. The Sume docs describe image_to_video as an input-shape route, so you do not switch endpoints. The next clip is a new job with its own price. If you used png, the frame is lossless but larger than the default jpeg, which suits a first frame.
Limits of the by-hand chain
A single frame carries the pose and the framing but not the motion in progress. Google's extension feeds up to 10 seconds of context, so it can continue a movement. A frame chain restarts the motion, which you can mask with a prompt that names the continuing action. Sume's Omni route also takes up to 3 reference clips of at most 3 seconds each, addressed as <VIDEO_REF_0>. That is a second carrier for motion, but it is a reference for a fresh generation, not an extension.
Each hop adds a 10-second job, a frame job and a few seconds of waiting. For a 40-second piece, that is four video jobs and three frame extractions. The frame jobs bill by Modal compute and are small next to the clips; the exact amount is on the receipt.
Keep the margin small. A margin of one frame period is enough for a clip at 24 frames per second, and the returned frame is still the last one you can see.
Sources
Related posts
More in Developers
- Video-router image_url to /v1/videos frame_images, in Python
Map image_url, end_image_url and reference_image_urls from /v1/video-router/generate to frame_images and input_references on /v1/videos, with Python.
- LinkedIn API sunsets: 202510, 202511 and 202601 dates, and the upgrade
LinkedIn lists 202510 sunsetting October 15, 2026, 202511 on November 16, 2026 and 202601 on January 15, 2027. Pick one target and test with a Sume clip.
- LinkedIn missing or deprecated version header errors: fail in CI
LinkedIn answers a missing or deprecated Linkedin-Version header with an error response. Check the header in CI, before a video post fails at runtime.
- LinkedIn-Version has no default: pin YYYYMM in your video poster
LinkedIn does not apply the latest API version when the header is missing. Pin a YYYYMM value in config and fail loudly if it is unset.
Written by Sume