Reddit 15-second ad: find the best 15 seconds with video frames
Before cutting a long ad to 15 seconds for Reddit, pull up to 24 stills with POST /v1/video-frames, one every two seconds, and choose where the cut starts.

To decide which 15 seconds of a longer ad to keep for Reddit's Engaged Video Views, pull stills first: POST /v1/video-frames with fps: 0.5 over a clip gives you a frame every two seconds, up to 24 per call, and you pick the start for the cut by eye. A trade tracker lists the Reddit bid as a public beta from 2026-09-01, where videos longer than 15 s bill at 15 s (read 2026-10-06), so only the first 15 seconds of what you upload are counted.
Sources: the Social media platform updates, October 2026 (read 2026-10-06) for the Reddit facts, which I could not confirm on Reddit's own help page, and Video frames: stills at times you name for Sume. Cut with Video trim: cut a range from one Sume-hosted clip afterwards.
Why look before you cut
A blind cut of seconds 0 to 15 keeps whatever the master happened to open with: a logo card, a slow fade, a creator saying hello. If the actual hook sits at second 6, the better cut starts at 6. Stills are cheaper to review than a render, and a contact sheet of 15 images shows you the whole ad in a glance, so you can see where the product first appears and where the offer shows.
How the sampling works
fps must be above 0 and at most 2, and Sume expands it to mid-bin samples (0.5/fps, 1.5/fps, and so on), capped at 24 frames per call, so fps: 0.5 covers 48 seconds with 24 frames at 1, 3, 5 and onward. For a clip longer than that, send at[] with explicit times instead. The source must be a media.sume.com clip of at most 300 seconds. A frame that fails comes back with a null url without failing the whole job.
import json, os, time, urllib.request
H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
"Content-Type": "application/json"}
def call(method, url, body=None, extra=None):
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(url, data=data, method=method,
headers={**H, **(extra or {})})
with urllib.request.urlopen(req) as r:
return json.load(r)
sub = call("POST", "https://api.sume.com/v1/video-frames", {
"video_url": "https://media.sume.com/artifacts/artf_demo/ad-40s.mp4",
"fps": 0.5,
"max_edge": 540,
}, {"Idempotency-Key": "reddit15-scan-001"})
rid = sub["request_id"]
while True:
res = call("GET", "https://api.sume.com/v1/video-frames/" + rid)
if res.get("resource_status") in ("ready", "failed"):
break
time.sleep(3)
for f in res.get("video_frames", res).get("frames", []):
print(f["t"], f["url"])
Errors you can hit, and an alternative
Send only one of at[] or fps; both together, none at all, more than 24 at values or an fps above 2 returns a schema 400. An at time outside the clip, for example 41 on a 40-second ad, fails the job with frame_time_out_of_range and tells you the duration the worker probed, and a source longer than 300 seconds fails with duration_out_of_range. When the source is longer than 90 seconds the result can carry a low_confidence_long_video warning, which does not fail the job (Video frames: stills at times you name).
Frames come back as JPEG by default, or PNG if you ask for lossless inspection with format. max_edge clamps the long edge between 16 and 2160 pixels; leave it out and the stills keep the source size, which is more than you need for choosing a start time. The job does not use an admission seat, and Sume bills it by its own Modal compute rather than as a generation, so it is a cheap step to run on every candidate ad.
If you want evidence about the whole clip in one call, the video inspect route returns a probe, eight mid-bin stills and optional speech-to-text, with a default max_edge of 768. Use that when you need the transcript to find where the offer is spoken, and use video frames when you want denser stills over a time range you already suspect.
Reading the contact sheet
Look for three things. First, the earliest frame where the product is clearly visible. Second, the first frame with a face reacting or a motion that stops the scroll. Third, where the offer or call to action appears. Choose a start that puts the first of these near the beginning, and check that the offer still falls inside start + 15. If it does not, a simple cut cannot save the ad, and you need a re-edit from separate clips.
| Item | Value | Why it matters |
|---|---|---|
| fps | Above 0, at most 2 | 0.5 gives a frame every 2 s |
| Frames per call | 24 at most | Use at[] to aim past 48 s |
| Source length | Up to 300 seconds | Longer clips need a trim first |
| Reddit billing rule | Videos over 15 s bill at 15 s | Only the first 15 s count |
| max_edge | 16 to 2160 px | 540 keeps review images small |
Then cut
When you have a start time, send it to video trim with duration: 15. Trim is one $0.02 job at the public rate on its page, and it leaves the source untouched, so you can try a second start time if the first cut does not feel right.
Sources
Related posts
More in Use cases
- Reddit's 15-second video views: what a 15 s clip costs on Sume models
Reddit bills videos over 15 s at 15 s in its Engaged Video Views beta. A 15-second clip costs $1.88 on Wan 3.0 at 720p or $2.10 on Kling 3 without sound.
- Trim 50 ads to 15 seconds for Reddit: the batch costs $1.00
Fifty finished ads cut to 15 seconds with /v1/video-trim at $0.02 a job is $1.00 before retries. A Python loop with one idempotency key per ad.
- Restaurant table tent art: Ideogram 4.5 at 3:4, specials quoted
Make table tent artwork for a restaurant: a 3:4 Ideogram 4.5 image with each special quoted in the prompt, a price check, and the real cost per tent on Sume.
- SaaS launch week asset budget: images, voiceovers and clips on Sume
A launch week of 12 Ideogram images, six 450-character voiceovers, six 5-second Grok clips and one music track costs about $1.61 on Sume. The per-line bill.
Written by Sume