UGC-style ad: test five hooks on one body with Timeline plans

Join five 3-second hooks to one 12-second body clip with Timeline 1.0. Plan each cut unbilled, then render the winners at $0.10 a minute.

5 min readSume
All posts

To test five opening hooks on a UGC-style ad, keep one body clip and put each hook in front of it with Timeline 1.0: one document per hook, each with two video[] slots. POST /v1/timeline-1.0/plan checks all five without charging you, and a render is $0.10 per output minute, rounded up, so each 15-second variant is one billable minute. Five variants rendered would be 5 x $0.10 = $0.50.

Source: Timeline 1.0: assemble clips into one MP4. Confirm the live rate in GET /v1/catalog. This post is about assembly; making the hook clips themselves is a separate step.

Why test hooks and not whole ads

Most of what makes a UGC-style ad work is its first seconds. If you change only the opening, you can read the result as a hook test: same offer, same voice, same ending. Rebuilding five whole ads confuses hook with everything else. A Timeline document is data, so five variants are one template with one value changed.

One template, five documents

The audio spine carries the voice. Each variant needs a spine of the same length as the output: here 3 seconds of hook plus 12 of body is 15, so audio.duration_seconds is 15. If hook voice differs per variant, audio.parts[] joins up to 20 gapless slices at the sample level, no re-TTS. If the voice is only in the body, use one spine file and set the hook slots without audio concerns.

The script builds five documents, plans each and prints the planned duration and cost estimate. A document is invalid if video[0].start is not 0 or if slot starts do not increase, and the plan says so before any money moves.

import json, os, urllib.request

BODY = "https://media.sume.com/artifacts/artf_demo/body-12s.mp4"
SPINE = "https://media.sume.com/artifacts/artf_demo/voice-15s.wav"
HOOKS = [f"https://media.sume.com/artifacts/artf_demo/hook-{i}.mp4" for i in range(1, 6)]

def plan(hook):
    doc = {
        "audio": {"url": SPINE, "duration_seconds": 15},
        "video": [
            {"source_url": hook, "start": 0, "duration": 3},
            {"source_url": BODY, "start": 3, "duration": 12},
        ],
    }
    req = urllib.request.Request(
        "https://api.sume.com/v1/timeline-1.0/plan",
        data=json.dumps(doc).encode(),
        headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
                 "Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req) as r:
        return json.load(r)

for hook in HOOKS:
    p = plan(hook)
    print(hook.rsplit("/", 1)[1], p["duration_seconds"], p["estimated_cost_usd_micros"])

What the numbers should be

Every plan should report 15 seconds and one billable minute, because the plan is computed from the numbers you declare, not from the files. It cannot see that a hook file is really 2.5 seconds long. If a source is shorter than its slot, the render pads or loops it and records a soft warning in warnings[], so open the warnings of each render before you ship it.

Cost of a five-hook test from Sume's Timeline 1.0 page, read 2026-10-06.
ItemCountRateTotal
Plan calls5Unbilled$0.00
Renders, 15 s each5$0.10 per ceil minute$0.50
Captions on each (optional)5$0.20$1.00
Plans, renders and captions together15 callsmixed$1.50

After the test

A hard cut from hook to body is the default. If a hook ends on a different framing from the body, you can add a transition to the body slot, since transitions are allowed only on slots after the first: fade, wipeleft, wiperight, slideup, slidedown or dissolve, at most 1 second and at most half of the shorter neighbouring slot. Keep the same transition in all five documents, otherwise you are testing two things at once.

Count the money before you start. Plans are unbilled, a render reserves ceil(audio.duration_seconds / 60) minutes, and a standalone caption job is $0.20, so five captioned variants come to $1.50 in total. Plan all five, fix whatever the plans reject, and only then render.

Keep the winning hook and drop the rest. If a hook wins on watch time, make three new hooks in its style and test again. The stored post on ten ad variants from one avatar take covers a larger version of this with trims and captions.

Sources

Related posts

More in Use cases

All Use cases posts

Written by Sume