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.

5 min readSume
All posts

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.

Frame sampling and the 15-second rule, from Sume's video frames page and a trade tracker, read 2026-10-06.
ItemValueWhy it matters
fpsAbove 0, at most 20.5 gives a frame every 2 s
Frames per call24 at mostUse at[] to aim past 48 s
Source lengthUp to 300 secondsLonger clips need a trim first
Reddit billing ruleVideos over 15 s bill at 15 sOnly the first 15 s count
max_edge16 to 2160 px540 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

All Use cases posts

Written by Sume