Timeline body from a cut list in TypeScript: starts and coverage
Turn a cut list of source ranges into Timeline slots: starts are running sums rounded to 3 decimals, and 3 ranges of 3.55, 4.68 and 3.36 s make 11.59 s.

A pause cut is a list of source ranges, but a Timeline body needs output-timeline start values and an audio.duration_seconds that agrees with them. Compute the starts as running sums of range lengths, round to 3 decimals, and floor the total so the audio never claims more than the parts hold; three ranges of 3.55, 4.68 and 3.36 s start at 0, 3.55 and 8.23 and total 11.59 s.
The mapping
Limits to enforce before you send: at most 20 audio.parts[], at most 200 video slots, and each slot duration at least 0.2 s. Past 20 ranges use a two-level concat.
| Range (source) | Duration | Output start |
|---|---|---|
| 0.55 to 4.10 | 3.55 s | 0 |
| 4.62 to 9.30 | 4.68 s | 3.55 |
| 9.84 to 13.20 | 3.36 s | 8.23 |
| Total | 11.59 s |
TypeScript
Pass the same source and detached wav URLs for every entry; the function keeps picture and audio parts aligned by construction.
type Range = { start: number; end: number };
const r3 = (n: number) => Math.round(n * 1000) / 1000;
export function timelineBody(ranges: Range[], videoUrl: string, wavUrl: string) {
if (ranges.length > 20) throw new Error("audio.parts is capped at 20");
let at = 0;
const video: object[] = [], parts: object[] = [];
for (const { start, end } of ranges) {
const duration = r3(end - start);
if (duration < 0.2) throw new Error("slot under 0.2 s");
video.push({ source_url: videoUrl, source_in: start, start: r3(at), duration });
parts.push({ url: wavUrl, source_in: start, duration });
at += duration;
}
const total = Math.floor(at * 1000) / 1000; // never claim more than parts hold
return { audio: { parts, duration_seconds: total }, video };
}Gotchas
- Keep one rounding rule for starts and durations, or a 1 ms gap appears between slots.
- Coverage can stop at most 0.5 s before the end of the spine, so the last slot must reach nearly to the end of the audio.
- Run
POST /v1/timeline-1.0/plan(unbilled) to readduration_secondsandbillable_minutesbefore the render.
Sources
Related posts
More in Developers
- Build the pricing_skus key from the resolution string: 4K is uppercase
Omni Flash 1.1 rates sit at per-video-second-360p, -720p, -1080p and -4K. Build the key from a template, mind the 4K case, total 10 s from $0.375 to $3.75.
- Which Sume API routes work without a key? Six public routes
Six Sume routes need no API key, including GET /v1/catalog and GET /v1/health. What they return, how they are limited, and a Python preflight for CI.
- Can polling hit the Sume read limit? 24 Pro jobs at 2 s use 6%
Polling every accepted job every 2 seconds uses 3.75% to 7.5% of a Sume plan's read budget. The arithmetic for Free, Pro, Startup and Scale, with the caveats.
- Cancel 12 queued Sume jobs: 12 writes, and a 409 that is not an error
Cancel is a write, only works before generation starts, and returns 409 job_generation_already_started after. A Python sweep that treats 409 as fine.
Written by Sume