video-adsffmpeg

Meta ads video variants aren't new shoots. They're hook swaps.

··10 min read
Meta ads video variants aren't new shoots. They're hook swaps.

Meta moved age and gender down from the ad set to the individual creative on Monday, October 19, and every write-up so far explains the targeting plumbing. None mention that the feature multiplies your render count in Black Friday build week. One ad set that needed one cut now wants eight, and they all have to be the same video.

Quick answer: Meta ads video variants that each carry their own age and gender only work when the surrounding ad set stays broad (minimum age 18 to 25, maximum age 65, all genders) and Advantage+ audience is off. The production job is then one cut per persona from a single master clip: keep the body footage identical and change only the opening hook bar, with ffmpeg -i master.mp4 -vf "drawbox=...,drawtext=text='your hook'" out.mp4 per variant. Or send the same master to FFmpeg Micro once and fire one API call per hook, with no encoder to host and failed renders never billed.

What changed on October 19, and what it costs you in renders

Meta opened a Marketing API option called "Choose an audience for each ad creative," which attaches its own age range and gender to each image or video inside a single ad set, per PPC Land's launch report. The field is creative_audience_pairing_persona, and on a multi-media ad it sits per asset inside media_sourcing_spec with age_min, age_max, and a genders array where 1 is male and 2 is female, per Relevant Audience's breakdown of the API shape.

Meta's own examples are demographic by creative: a retirement-planning video to the 50 to 65 slice, women's sweaters to women, men's sweaters to men. It rewards accounts that can produce a distinct cut per slice, and punishes shipping the same video four times with a persona bolted on.

Access is Marketing API partners first, but Jon Loomer found the setting in Ads Manager the same week, so the rollout is staged and your account may not show it yet.

Your ad set has to stay broad, or the option disappears

Per-creative personas only appear when the ad set around them stays open. Minimum age has to sit between 18 and 25, maximum age has to be 65, and gender is either left out or set to both values. A single-gender ad set is rejected. You also cannot combine it with Advantage+ audience or Meta's age and gender suggestions, which AdShift's changelog entry spells out alongside the enable-at-ad-set-then-set-per-ad sequence.

That is why the toggle vanishes on first try: the ad set you duplicated from last quarter had women 25-54 locked in, or Advantage+ was on. Clear both, then set personas on the creatives.

Only the hook changes between variants

The variable you're testing is the opening line, so the footage underneath has to be identical across every cut. AI ad-multiplier tools that regenerate footage with a different cast, setting, or product change three variables at once, so a win tells you nothing about which one won.

Hook-variant testing is the boring standard for a reason. One core video body, several opening lines, launched together so the auction allocates. AdManage's read on creative volume for 2026 puts 3 to 6 ads per ad set as the band for most tests, 3 as the floor for a usable signal, and roughly 50 optimization events a week as the real limiter. Per-creative personas push you toward the top of that band, not past it.

We made the same argument about aspect ratios in don't generate bulk video ad variants, reframe your master clip. Here the test design depends on it.

Rendering the hook bar: FFmpeg and the one-call version

A hook bar is two filters: a solid rectangle and centered text inside it. No timeline, no editor, no per-variant project file.

Where the bar can legally sit

For a 1080x1920 render that runs on both Reels and Stories, keep text out of the top 14 percent and the bottom 35 percent, plus the right-edge strip. Reels is stricter of the two; Blip's safe-zone guide has the measurements. On a 1920-pixel-tall frame that leaves roughly y=269 to y=1248 as usable. A bar at y=300 with h=240 ends at 540, comfortably inside.

The drawtext command

Put your hooks in a CSV with the persona they target, then loop:

while IFS=, read -r hook_id hook age_min age_max genders; do
  ffmpeg -y -i master.mp4 \
    -vf "drawbox=x=0:y=300:w=iw:h=240:color=0x1F2937@0.92:t=fill,\
drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:\
textfile=hooks/${hook_id}.txt:fontcolor=white:fontsize=64:line_spacing=14:\
x=(w-text_w)/2:y=300+(240-text_h)/2" \
    -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p \
    -c:a aac -b:a 128k -movflags +faststart \
    "bfcm26_m07_9x16_${hook_id}_a${age_min}-${age_max}_g${genders}.mp4"
done < hooks.csv

textfile= instead of text= is deliberate. Escaping is the biggest time sink in this job. Hook lines are full of apostrophes, colons, and commas, all filtergraph metacharacters. "Don't wait: the sale ends Friday" inside text= produces Unable to parse option value or renders half the line. A file per hook sidesteps the escaping entirely.

The same job as one API call

The API version differs in a way that matters at ten variants: you upload the master once and reuse it for every render. Get a presigned URL from POST /v1/upload/presigned-url, PUT the file to it, confirm with POST /v1/upload/confirm, then submit one transcode per hook:

curl -s -X POST https://api.ffmpeg-micro.com/v1/transcodes \
  -H "Authorization: Bearer $FFMPEG_MICRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fileId": "'"$FILE_ID"'",
    "filters": [{
      "filter": "@text-overlay",
      "text": "Retirement is closer than you think",
      "fontSize": 64,
      "charsPerLine": 24,
      "lineSpacing": 14
    }]
  }'

Each call returns a job id. There are no completion callbacks: submit, poll, fetch the output. The full option list for @text-overlay is in the docs. Ten jobs submitted back to back run in parallel on our side. On one laptop, libx264 chews through them one at a time. Nothing to install, no font packages to chase, no -pix_fmt yuv420p footgun to remember, and the free tier gives you 200 tokens at sign-up with no card.

The same pattern at larger scale is our bulk shorts walkthrough: a CSV of lines, a folder of clips, 100 renders in parallel.

A filename scheme that carries the persona

Meta shipped this feature with no reporting breakdown for how each creative's audience performed, which makes your filename load-bearing: the ad name and the file name are the only link between a rendered variant and the age and gender attached to it. Ad-level naming has to encode the variant explicitly, as AdsUploader argues, because individual asset names are not exposed cleanly in reporting.

Encode Meta's own values so the mapping is mechanical:

  • bfcm26 campaign shorthand
  • m07 master clip id (which body footage this is)
  • 9x16 ratio
  • h03 hook id, matching the row in your CSV
  • a50-65 the age_min and age_max you send
  • g0 the genders array, where 0 means omitted (all), 1 male, 2 female

That gives bfcm26_m07_9x16_h03_a50-65_g0.mp4. Use the same string as the ad name. A split by ad name is then the persona breakdown Meta did not give you, traceable to one hook line and one demographic slice.

What a ten-variant sweep costs

Ten hook variants from a 20-second master is ten jobs of 20 seconds of input, 200 seconds total. On FFmpeg Micro that is 20 tokens, because one token covers 10 seconds of input, metered per second. The 200 free tokens at sign-up cover the sweep ten times over. On Starter at $19.99 for 2,500 tokens, the set runs about 16 cents, under two cents per variant.

Three details make a variant sweep safe to run on a deadline:

  • Billing is on the duration of video processed, not how long the servers take. A heavy filtergraph costs the same as a light one.
  • Per second, with no rounding up to the minute. A 20-second video costs 20 seconds.
  • Failed jobs are never billed. Submit twelve, throw away four for a typo in the hook copy, pay for eight.

That last one is the practical difference from a per-generation AI tool: discarding a bad batch is free. The full numbers are on pricing.

Common pitfalls

The drawtext escaping problem is the one everyone hits first. Three more before you render eight files you have to redo.

Re-encoding can strip Content Credentials. Meta's updated Self-Serve Ad Terms, effective October 30, 2026, say advertisers must not alter or remove watermarks or content-authenticating metadata generated by AI. C2PA credentials live in a JUMBF box, and an ordinary FFmpeg re-encode drops boxes the muxer does not recognize. Check the output before burning a hook bar onto a master from an AI video model.

Inconsistent specs across variants wreck the comparison. If one cut lands at CRF 20 and another at CRF 28, you are testing compression, not copy. Render every variant from one command, changing only the text input.

Hook text that overflows the bar. text_h grows with wrapped lines, so a 60-character hook at fontsize=64 spills past h=240. Cap hook copy at a character count you have tested, which is what charsPerLine handles on the API side.

When per-creative variants are the wrong move

Hook swapping is wrong when what should differ between demographics is the footage itself. A product demo for a 20-year-old and a 60-year-old may need different hands, setting, and pace, which text on identical footage cannot fake. That is a reshoot or a generative pass, and you lose the clean single-variable read.

Hook swapping also fails when your ad set cannot feed the variants. Ten creatives in one ad set means each gets a fraction of the signal, so run 3 to 6 and add more only when spend supports it.

And if you need the same hook across four aspect ratios rather than four hooks in one ratio, that is a reframe job, not a text job. We covered that split in your TikTok Shop Black Friday video isn't one file, it's eight and the render-count math in Meta generative video ads still leave you 3 render jobs.

FAQ

Does my ad set have to be broad, or just broad-ish?

Per-creative audiences require the ad set's minimum age to be between 18 and 25, the maximum age to be 65, and gender either omitted or set to both values. Anything narrower and the option is unavailable.

Why does the per-creative audience option disappear when Advantage+ audience is on?

Per-creative personas and Advantage+ audience are mutually exclusive, along with Meta's age and gender suggestions. Advantage+ decides targeting for the whole ad set, the same job the per-creative persona does per asset, so Meta does not let both run.

If Meta gives no per-persona breakdown, how do I tell which cut won?

Your ad name is the breakdown. Encode the hook id and the exact age range and gender values into the file name, reuse that string as the ad name, then split results by ad name. Meta shipped no reporting view for creative-level audiences, so the mapping has to be yours.

Should the footage change between variants, or only the opening hook?

Only the opening hook should change. Identical body footage is what makes the test readable, and it keeps the render cheap: you upload one master and fan out text overlays instead of producing new video for each slice.

Does the persona guarantee my creative only reaches that audience?

No. Meta calls the persona the audience a piece of media is "best suited for" and has not committed to excluding everyone else, so a creative tagged 50 to 65 may still be served to others likely to respond. It's a strong hint to delivery, not a hard wall, so plan hooks that do not embarrass you off-target.

The automated version is the Text Hook Variants blueprint: upload one video, type up to three hooks, get back three ads with a different opening line on a colored bar and identical specs underneath. At BFCM volume, the API and the n8n, Make, and Zapier paths are on video ads.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Skip the command line

The Text Hook Variants blueprint runs the same job for you: upload, done.

Run it (free)