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.

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.
| Item | Count | Rate | Total |
|---|---|---|---|
| Plan calls | 5 | Unbilled | $0.00 |
| Renders, 15 s each | 5 | $0.10 per ceil minute | $0.50 |
| Captions on each (optional) | 5 | $0.20 | $1.00 |
| Plans, renders and captions together | 15 calls | mixed | $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
- UGC-style ad: a music bed that ducks under the voice
Add a looped music bed to a UGC-style ad and lower it under the voice with soundtrack.duck_db in a Timeline 1.0 render. Needs a real voice spine.
- UGC-style spokesperson video script: hook, proof, ask in 45 seconds
A 126-word UGC-style script split into three scenes for Sume Avatar 1.0, with a silence beat, a product image, the request and the per-tier price.
- Vertical workout demo, 15 seconds at 720p: Sume prices
A 15-second 9:16 workout clip at 720p is $1.13 on H3, $1.88 on Wan 3.0, $3.15 on Kling 3 with sound and $8.67 on Seedance 2.5. Omni Flash stops at 10 s.
- VHS camcorder look on purpose: 360p and 480p AI video on Sume
A 6-second 360p or 480p AI video costs $0.23 to $0.53 on Sume. Omni Flash 1.1 is the only video id with a 360p rung; the others start at 480p.
Written by Sume