Rehearsal dinner slideshow: 24 photos and Timeline's 8-fade cap

A 24-photo slideshow with fades fails if every cut is a fade: Timeline refuses more than 8 chained transitions. Insert hard cuts; 96 seconds costs $0.20.

6 min readSume
All posts

To make a rehearsal dinner slideshow from 24 photos with fades, put each photo in its own video[] slot and add a fade transition on most of them, but leave a hard cut after every seventh fade. Timeline 1.0 refuses a chain of more than 8 adjacent fades with too_many_chained_transitions, so a fade between all 24 photos is rejected. At 4 seconds a photo the render is 96 seconds, which bills as 2 minutes at $0.10 per ceil(output minute): $0.20.

The rules come from the Timeline 1.0 page, read 2026-10-04.

The numbers

Timeline takes one audio spine and an ordered list of slots. Stills are static holds, start values must increase from 0, and every URL has to be a media.sume.com artifact, so import the photos first.

Slideshow arithmetic against the Timeline 1.0 limits (read 2026-10-04)
ItemValueRule
Photos24video[] takes 1 to 200 slots
Seconds per photo4On-screen length of at least 0.2 s
Output length96 saudio.duration_seconds, 1 to 1800
Billable minutes2ceil(96 / 60)
Price$0.20$0.10 per ceil(output minute)
Fade length0.5 sAt most 1 s and at most 50% of the shorter neighbour
Chained fades7, 7, 7More than 8 adjacent fades is refused

Build the body in Python

Slots 2 to 8 get fades, slot 9 starts with a hard cut, slots 10 to 16 get fades, slot 17 is a hard cut, and slots 18 to 24 get fades. That is three runs of 7. Output fps is set to 30 because a 0.5 second fade must be frame-aligned and 0.5 s is not a whole number of frames at 25 fps. The sound is a track you already imported, under a silent spine.

Run the same body against POST /v1/timeline-1.0/plan first. The plan is unbilled and returns duration_seconds, segment_count, billable_minutes and estimated_cost_usd_micros.

import json

photos = [f"https://media.sume.com/artifacts/artf_demo/photo-{i:02d}.jpg" for i in range(1, 25)]
SECONDS = 4
slots = []
for i, url in enumerate(photos):
    slot = {"source_url": url, "start": i * SECONDS, "duration": SECONDS, "fit": "blur"}
    # slot numbers are 1-based; slots 1, 9 and 17 start a new run of fades
    if i % 8 != 0:
        slot["transition"] = {"type": "fade", "duration": 0.5}
    slots.append(slot)

body = {
    "audio": {"mode": "silence", "duration_seconds": len(photos) * SECONDS},
    "soundtrack": {"url": "https://media.sume.com/artifacts/artf_demo/bed.mp3", "fade_out_seconds": 3},
    "video": slots,
    "output": {"width": 1920, "height": 1080, "fps": 30},
}
print(json.dumps(body, indent=2))

Plan, then render

Send the printed JSON to /v1/timeline-1.0/plan with curl -d @body.json, then to /v1/timeline-1.0/render with an Idempotency-Key header once the plan shows 2 billable minutes. Past 12 segments the default render.strategy of auto renders in chunks; do not force single, which is refused above 12 slots as render_strategy_unsafe.

fit: "blur" keeps each whole photo in frame over a blurred copy of itself, which suits a folder of phone photos that mix portrait and landscape.

  • Plan: free, no job, no reservation.
  • Render: Idempotency-Key required; default mode is async, so poll GET /v1/jobs/:id/status.
  • A fade longer than the spine allows is refused (edge_fades_exceed_output); keep fade_out_seconds well under 96.

Keep the runs short

Sume's docs do not say whether a dissolve counts differently from a fade in the chain limit, so treat every transition as one link. If you want a different look between runs, change the type to dissolve or slideup on one run and keep each run at 7 or fewer.

Sources

Related posts

More in Use cases

All Use cases posts

Written by Sume