Timeline segment_overlap: set start times when clips cross-fade
Sume Timeline returns segment_overlap when a slot starts before the previous one ends minus the fade. The rule, the fix, and a Python check.

Timeline 1.0 answers 400 segment_overlap when video[i].start is earlier than the previous slot's start + duration minus the length of slot i's own transition. Fix it by moving the start later, or by lengthening the fade. If every start is simply the sum of the durations before it, the error cannot happen, with or without fades.
The related code invalid_segment_timing fires in two cases: a start that is not strictly greater than the previous start, or a slot that runs more than 0.5 s past audio.duration_seconds. Both are caught at submit, so you pay nothing for the mistake.
What exactly does segment_overlap compare?
In the Timeline 1.0 reference the refusal is described as "overlap past the xfade". The API check (apps/api/src/schemas.ts and apps/api/src/timeline.ts) is one inequality per slot after the first: start >= previous.start + previous.duration - transition.duration, where the transition duration is 0 for a hard cut. A 0.25 s fade therefore lets a slot start up to 0.25 s before the previous one ends, and not a frame earlier.
Every slot declares an on-spine start, and the docs say those starts are authoritative: the compiler compensates for the cross-fade and never pre-shifts a slot. That is the practical difference from raw FFmpeg, where you work out an offset yourself.
| Code | When it fires | Fix |
|---|---|---|
| segment_overlap | start < previous start + previous duration - this slot's transition duration | Move the start later, or lengthen the fade |
| invalid_segment_timing | start is not greater than the previous start | Make starts strictly increase |
| invalid_segment_timing | a slot ends more than 0.5 s after audio.duration_seconds | Shorten the slot or lengthen the spine |
| timeline_must_start_at_zero | video[0].start is not 0 | Set the first start to 0 |
How does that compare with FFmpeg's xfade offset?
The FFmpeg filters documentation defines xfade's offset as the cross-fade start relative to the first input, in seconds, with a default of 0 and a duration range of 0 to 60 seconds. It also requires both inputs to share resolution, pixel format, frame rate and timebase. A chain of N clips means N-1 offsets that you derive from every earlier duration and fade.
On Sume you write the start where the clip belongs on the audio spine, and the six transition types (fade, wipeleft, wiperight, slideup, slidedown, dissolve) are applied for you. Mixed sizes and frame rates are handled by fit and output, not by you.
Can I check starts before sending a job?
Yes. POST /v1/timeline-1.0/plan runs the same schema and compiler checks without creating a job, reserving credits or downloading media, and it needs no Idempotency-Key. It returns duration_seconds, segment_count, billable_minutes and an estimate. A plan cannot predict warnings about short sources that get padded or looped, so keep reading warnings[] on the finished render.
The Python below mirrors the two start rules, so you can lint a video[] array in a unit test. It runs as written with the standard library.
def first_error(video):
for i in range(1, len(video)):
prev, cur = video[i - 1], video[i]
d = cur.get("transition", {}).get("duration", 0)
if not cur["start"] > prev["start"]:
return i, "invalid_segment_timing"
if cur["start"] + 1e-9 < prev["start"] + prev["duration"] - d:
return i, "segment_overlap"
return None
slots = [
{"start": 0, "duration": 8},
{"start": 7.5, "duration": 8, "transition": {"type": "fade", "duration": 0.25}},
]
print(first_error(slots)) # (1, 'segment_overlap')
slots[1]["start"] = 7.75
print(first_error(slots)) # NoneWhat should I use as starts in the first place?
Accumulate the on-screen durations: slot 0 starts at 0, slot 1 at the sum of slot 0's duration, and so on. That satisfies the overlap rule for any transition length. Set audio.duration_seconds to that total, since the last slot may trail the spine by at most 0.5 s. Submit with Idempotency-Key, poll the job as described in Jobs and results, and read video_url from the result. Timeline renders are $0.10 per ceil(output minute) according to the Timeline page, which also says to confirm the live rate in GET /v1/catalog.
What should I log when it fails?
Log the slot index, the start you sent, the previous slot's start and duration, and the transition duration on the slot. The refusal names the offending slot, so the arithmetic is one subtraction away. Teams that generate timelines from a script usually find the same bug: the start was computed from the source file length instead of the slot's duration, so a clip trimmed with source_in still reserved its full length on the spine.
Keep one builder function that owns both fields. If duration is the only number you pass in, and start is always derived, the overlap refusal turns into a unit test you will never see fail in production, and the unbilled plan call becomes a last line of defence rather than a debugging tool.
Sources
Related posts
More in Developers
- Timeline transition_too_long: 1 s cap and half the shorter clip
transition_too_long means a Timeline fade is over 1 s or over half the shorter neighbour. Sub-frame fades fail too. Limits, snapping, and a check.
- Transactional outbox for paid API calls in Python (Sume)
Write the Sume request and its Idempotency-Key in the order's transaction, drain later: a tested Python outbox that survives crashes, 429s and 409s.
- Transcribe a three-hour recording when the API caps at ten minutes
Streaming sessions end at an hour; Sume STT jobs take up to 600 seconds. Split with Timeline audio, transcribe 18 chunks, and stitch word times back together.
- Translate an SRT and burn it in: Sume caption cues, limits, Python
Sume takes no SRT upload, but caption cues take the same text and times. A Python converter, the 200-cue and 60-second limits, and which fonts apply.
Written by Sume