Short ends before the voice: timeline_gap_filled and last-frame hold
When Timeline clips cover less than the audio, you get a held last frame and a warning, not an error. The two codes, and how to fix them before upload.

If your video slots end before the audio does, Sume still renders. The last frame is held to the end of the audio and the result carries the warning video_coverage_shorter_than_audio; a hole between two slots is filled and flagged timeline_gap_filled. A render with warnings is billed like any other, so check warnings[] before you upload.
Both codes come from the repo's Timeline compiler and are described in the Timeline 1.0 doc; results are read through the jobs doc.
What exactly happens?
The spine sets the program length. Slots are placed by start and duration. Anything the slots do not cover is filled, so the viewer never sees black frames, but a frozen frame under live speech looks like a bug.
| Situation | Result | Warning code |
|---|---|---|
| Gap between two slots | Gap is filled | timeline_gap_filled |
| Slots end before the audio | Last frame is held | video_coverage_shorter_than_audio |
| Still image in a slot | Static hold, no motion | None, a still is a hold by design |
How do I catch it in code?
Poll GET /v1/jobs/:id/status until terminal, then read /result and test for the warning codes. Fail your own pipeline on them instead of publishing.
import os, requests
API = "https://api.sume.com"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
BAD = {"video_coverage_shorter_than_audio", "timeline_gap_filled"}
def main(job_id):
r = requests.get(f"{API}/v1/jobs/{job_id}/result", headers=H)
r.raise_for_status()
body = r.json()
found = [w.get("code") for w in body.get("warnings", []) if isinstance(w, dict)]
hit = BAD.intersection(found)
if hit:
raise SystemExit(f"fix coverage before upload: {sorted(hit)}")
print("ok")
main(os.environ["JOB_ID"])How do I fix it?
Extend the last slot's duration to the end of the audio, add another clip, or shorten audio.duration_seconds with a trim. The plan call, which is unbilled, returns the program length so you can compare it with your slots before you spend anything.
What Sume does not do
It does not decide whether a hold is acceptable. A held frame under a closing line may be fine; one under a mid-sentence is not.
Sources
Related posts
More in Developers
- Size a Recast batch from generation_limits, not wave_size_hint
Each Sume submit returns generation_limits and idempotency_hit. A Python loop computes headroom as concurrency minus active minus queued.
- Slack custom emoji under 128 KB: shrink a Sume image to fit
Slack says custom emoji should be square, under 128 KB, JPG, PNG or GIF. Generate a 1:1 mark on Sume, scale it small, and loop a PNG palette down to fit.
- Social search returned 202 after 30 s: follow the job, don't resend
Sume's social read routes wait up to 30 seconds, then answer 202 with a job. Poll that job; resending the body runs another upstream read.
- Sora video ids in your database after the shutdown: what to keep
OpenAI's Videos API shut down 2026-09-24. Old video_ids no longer resolve, so store your own file URL and the model used. Schema fields included.
Written by Sume