Check an Omni edit kept the rest of the clip: video-frames pairs
Compare stills from the source and the edited clip at the same timestamps with video-frames on Sume. A script that submits both extracts, plus what to look for.

To check that a Gemini Omni Flash edit kept everything else the same, extract stills from the source and from the edited clip at the same timestamps with POST /v1/video-frames, then compare each pair. The edit prompt asks the model to keep the rest of the scene, but the clip is regenerated, so only a side-by-side shows what moved.
Submit both extracts
Video frames takes one media.sume.com clip and either at[] (1 to 24 times, each from 0 up to but not including the duration) or fps. A submit always returns 202 with a request_id. Send an Idempotency-Key on REST so a retry does not queue a second extract. The script reads the two clip URLs from the environment and prints one request id for each.
import asyncio, json, os, urllib.request
def call(method, path, body=None, key=None):
headers = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}
data = None
if body is not None:
data = json.dumps(body).encode()
headers["Content-Type"] = "application/json"
headers["Idempotency-Key"] = key
req = urllib.request.Request("https://api.sume.com" + path, data=data, headers=headers, method=method)
with urllib.request.urlopen(req) as resp:
return json.load(resp)
async def main():
times = [0.5, 2.5, 4.5]
for name, url in {
"source": os.environ["SOURCE_URL"],
"edit": os.environ["EDIT_URL"],
}.items():
out = await asyncio.to_thread(
call, "POST", "/v1/video-frames",
{"video_url": url, "at": times}, "frames-" + name + "-001")
print(name, out["request_id"])
asyncio.run(main())Read the result
Poll GET /v1/video-frames/{request_id} until resource_status is ready. Each entry in frames has t, url, width and height. If a single instant fails, that frame has a null url, and the job still succeeds, so check for nulls before comparing. Add "format": "png" when you want lossless stills, or max_edge to clamp the long edge between 16 and 2160 pixels.
What to compare
Put the pair side by side and look in a fixed order.
| Look at | Why it drifts |
|---|---|
| The edited object and its edges | Shadows, reflections and halos are the usual leftovers |
| Faces and hands | Regenerated detail can change an expression or finger count |
| Text on signs and screens | Lettering is the first thing to warp |
| Background lines and patterns | Straight edges bend when the model repaints them |
| Framing | A small zoom or shift changes every pixel |
Automate the verdict, not the judgment
You can script a rough check once you have the stills: compare image dimensions (the width and height in each frame entry should match across the pair) and flag any pair with a null url. Beyond that, a person needs to look. A pixel diff will flag a legitimate edit as a difference and a subtle face change as noise, so use it to choose which pairs to open first, not to approve a clip.
Keep the stills with the job record. When a client says "it looked different last time", the pair from the day of the edit settles it.
Choose times that matter
Pick the start, a moment in the middle where the edit is most visible, and a point just before the end. With a clip of 6 seconds, [0.5, 3, 5.5] is enough; with a 30-second edit, use a longer list up to the 24-frame limit. Avoid exactly 0 and the final frame, since the last time must be less than the duration.
For a whole-clip read in one call, video inspect returns a probe and eight stills by default. Video frames is the one to use when you need the same timestamps on both clips.
Sources
Related posts
More in Developers
- Check an Omni edit's length with video-inspect before a timeline join
An edit should follow the source length. Confirm it with a probe-only video-inspect call before the clip goes into a Timeline render, with a Python read.
- Check duration, resolution, ratio against /v1/videos/models in Node
A Sora-era request will not fit every Sume model. A Node script reads GET /v1/videos/models and lists what the model rejects before you pay for a job.
- communication.webhook_url 400: HTTPS, public host, 2048 chars
A communication.webhook_url that is not public HTTPS, is over 2048 characters, or points at localhost or a private network returns 400 invalid_request.
- Connect a new MCP client to Sume: five calls that prove it works
After you add https://mcp.sume.com/mcp to a new client, run mcp_health, tools_list, tools_schema, account_me and catalog_list. What each result should show.
Written by Sume