How to make Black Friday ad variants from one video automatically

You have one good source clip and four weeks until Black Friday creative has to be locked. The ask is a testable ad set: several opening hooks, each one sized for TikTok, Reels, and feed placements. Opening an editor twelve times is how that turns into a lost week.
Quick answer: Black Friday video ad variants come from two layers applied to one source clip: a text hook burned onto a colored bar over the opening, and a reframe to each placement ratio (9:16, 1:1, 4:5, 16:9). In FFmpeg that is onescale/crop/padplusdrawbox,drawtextchain per output, so three hooks across four ratios is 12 renders you script, queue, and babysit locally. Send the same 12 chains to the FFmpeg Micro API as 12 jobs and there is no encoder to install, no render box to keep alive, and no per-render editor fee.
A Black Friday ad set is a matrix, not a pile of edits
The reason variant production feels expensive is that most people price it as editing work: one timeline per variant, one export per timeline. A Black Friday ad set is not shaped like that. It is a matrix with two axes, hook text and placement ratio, and every cell is the same 15 seconds of footage with two filters on top.
Three hooks across four ratios is 12 files. Eight SKUs is 96. That number is terrifying in an editor and boring in a pipeline, which is the whole point.
The timing pressure is real and it is dated. TikTok for Business's Black Friday ads guide says to "use September and October to test creative variations, CTAs, and placements. Scale only what works," and tells advertisers to start preparing in September, finalize creative and partnerships in October, then go live in early November. It also warns that Q4 creator slots fill fast. If October is for finalizing, the variant pipeline has to exist now.
Layer one: change the opening hook, keep the cut
A hook variant changes the first thing a viewer reads, not the footage. In FFmpeg that is two filters: drawbox to lay a solid colored bar across the frame, and drawtext to center the hook copy inside it.
ffmpeg -i master.mp4 -vf "drawbox=x=0:y=ih*0.18:w=iw:h=230:color=#E11D2A@1:t=fill,drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:text='48 HOURS ONLY':fontcolor=white:fontsize=60:x=(w-text_w)/2:y=h*0.18+(230-text_h)/2" -c:v libx264 -crf 20 -preset veryfast -pix_fmt yuv420p -c:a copy hook1.mp4
Two details matter more than they look. -c:a copy keeps the original audio untouched while the video is filtered, which is legal because only the video stream goes through the filter graph. Try to stream-copy the video as well and FFmpeg stops with filtering and streamcopy cannot be used together. And pix_fmt yuv420p is what keeps the file playable in every ad manager and preview player.
The same operation as one API call, with no font path and no binary on your machine:
curl -X POST https://api.ffmpeg-micro.com/v1/transcodes \
-H "Authorization: Bearer $FFMPEG_MICRO_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": [{ "url": "https://your-bucket.s3.amazonaws.com/master.mp4?X-Amz-Signature=..." }],
"outputFormat": "mp4",
"options": [
{ "option": "@text-overlay", "argument": { "text": "48 HOURS ONLY", "style": { "position": "center", "fontSize": 60 } } },
{ "option": "-crf", "argument": "20" }
]
}'
That returns {"id": "...", "status": "pending"}, and GET /v1/transcodes/:id gives you status plus outputUrl when it flips to completed. @text-overlay handles positioning, styling, and wrapping; if you want the exact bar geometry from the CLI version instead, pass the same chain as {"option": "-vf", "argument": "drawbox=...,drawtext=..."}. -filter_complex is not supported, so multi-input compositing goes through the top-level filters array rather than one giant graph. If you would rather not build the hook step at all, the Text Hook Variants blueprint is this exact job as a form: upload the clip, type up to three hooks, get three videos back with the bar already placed.
Layer two: batch resize the ad to every placement ratio
Reframing is the second axis, and it is where "batch resize video ads" becomes a decision rather than a command. Every placement wants a different shape, and Meta collapses its 25-plus placements onto just a few of them.
| Ratio | Pixels | Where it runs | Default fit |
|---|---|---|---|
| 9:16 | 1080x1920 | TikTok, Reels, Shorts, Stories | crop |
| 1:1 | 1080x1080 | Feed, carousel | crop |
| 4:5 | 1080x1350 | Meta feed | crop |
| 16:9 | 1920x1080 | In-stream, YouTube, Amazon listing | pad |
Two chains cover the whole table. To fill the frame and lose the edges:
scale=1080:1350:force_original_aspect_ratio=increase,crop=1080:1350
To keep the whole frame and add bars:
scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black
Pad when the frame is the product, crop when the subject is centered
Cropping is the default for social placements because letterboxed bars read as reposted content and eat the vertical space your hook needs. Crop when the subject sits near the middle of the frame and the edges are background. Pad when losing the edges loses information: a full product shot, an on-screen price, a wide table-top scene, packaging with text near the border. For a deeper pass on the four-ratio fan-out and Meta's placement mapping, see Meta ads video specs: all four ratios from one master file. When the job is only the reframe and not the hook, the Listing Kit blueprint does one video into all four variants in a single pass.
Keep the hook text out of the platform UI
Platform chrome sits on top of your video, and a hook that lands under the caption block or the follow button is a wasted variant. On a 1080x1920 TikTok render, budget roughly 108 px at the top and 320 px at the bottom for UI, with about 60 px on the left and 120 px on the right.
That is why the bar above sits at y=ih*0.18. On a 1920-tall frame that is 345 px down, clear of the top chrome, and its 230 px height ends at 575 px, nowhere near the 1600 px line where the bottom UI starts. Using fractions of ih instead of fixed pixels means the same chain places the bar sensibly in 1:1 and 4:5 too. These margins change when platforms redesign, and they are percentages in most published guides rather than pixels, so convert them to FFmpeg pixels per output size rather than reusing one number everywhere.
The n8n workflow that renders all 12 files
n8n does not need a local FFmpeg binary for any of this, which matters since the Execute Command node is no longer available on n8n Cloud. The whole matrix is a Code node that builds 12 filter strings and an HTTP Request node that submits them.
- A Set node holds the master clip URL and your API key.
- A Code node cross-joins hooks and ratios into 12 items, each with a finished
vfstring. - An HTTP Request node POSTs each item to
https://api.ffmpeg-micro.com/v1/transcodes. - A Wait node plus a second HTTP Request node polls
GET /v1/transcodes/{{$json.id}}untilstatusiscompleted. - The final node collects every
outputUrlinto a sheet or an upload step.
The Code node, ready to paste:
const hooks = ['48 HOURS ONLY', 'BLACK FRIDAY 40% OFF', 'ENDS MONDAY'];
const ratios = [
{ name: '9x16', w: 1080, h: 1920, fit: 'crop' },
{ name: '1x1', w: 1080, h: 1080, fit: 'crop' },
{ name: '4x5', w: 1080, h: 1350, fit: 'crop' },
{ name: '16x9', w: 1920, h: 1080, fit: 'pad' },
];
const out = [];
for (const hook of hooks) {
for (const r of ratios) {
const reframe = r.fit === 'crop'
? `scale=${r.w}:${r.h}:force_original_aspect_ratio=increase,crop=${r.w}:${r.h}`
: `scale=${r.w}:${r.h}:force_original_aspect_ratio=decrease,pad=${r.w}:${r.h}:(ow-iw)/2:(oh-ih)/2:color=black`;
const barH = Math.round(r.h * 0.12);
const size = Math.round(r.w / 18);
const safe = hook.replace(/[':\\]/g, '');
const bar = `drawbox=x=0:y=ih*0.18:w=iw:h=${barH}:color=#E11D2A@1:t=fill`;
const text = `drawtext=text='${safe}':fontcolor=white:fontsize=${size}:x=(w-text_w)/2:y=h*0.18+(${barH}-text_h)/2`;
out.push({ json: { name: `${r.name}-${safe.slice(0, 12)}`, vf: `${reframe},${bar},${text}` } });
}
}
return out;
The same structure works in Make (Iterator into an HTTP module) and Zapier (Looping into Webhooks by Zapier). The free tier is enough to render a first matrix end to end before you commit to a volume; per-job cost after that is on the pricing page.
Common pitfalls in variant batches
Order the filters reframe-first, draw-second. If you burn the hook bar into the 16:9 master and then crop to 9:16, you slice the ends off your own text. Building each cell as a single chain from the untouched master avoids it and saves a generation of re-encoding.
Use explicit even dimensions. Ratio math like crop=iw*4/5:ih on an arbitrary source produces odd widths, and libx264 stops with "width not divisible by 2." Hard-code 1080x1350 and let force_original_aspect_ratio do the fitting.
Sanitize the hook copy. drawtext treats apostrophes, colons, commas, and backslashes as syntax, so "Don't miss it: 40% off" breaks the filter parse rather than rendering. Strip or escape them, load the text from a file with textfile=, or use @text-overlay and skip the escaping problem.
Check the audio codec once, not twelve times. If the master's audio is not AAC, every variant inherits the problem, and Instagram's publishing container answers with error 2207052 instead of telling you why.
When a batch pipeline is the wrong tool
A render matrix changes text and geometry. It does not change footage. If your hook test means a different opening shot, a different voiceover line, or a re-ordered scene, that is an edit, and a timeline-based template service handles scene-level changes better than a filter chain does.
Two more boundaries worth stating plainly. The hook-bar variant is a paid-social asset, not an Amazon listing asset: Amazon bounces third-party branding, logo-first openings, and variable frame rate, so cut the marketplace 16:9 from the clean master instead. And a pipeline tells you nothing about which hook worked. That answer lives in Ads Manager and Creative Analytics, which is also why TikTok's guide says to change one element per test.
FAQ
How many Black Friday ad variants should I make from one video?
Start with three hooks across the four placement ratios, which is 12 files from one source clip. Three hooks is enough spread to learn something without splitting budget so thin that no variant gets out of the learning phase, and it matches the one-variable-per-test rule that TikTok's own creative guidance recommends.
Should I pad or crop when resizing a video ad?
Crop for 9:16, 1:1, and 4:5 social placements, and pad for 16:9 when you need the whole frame intact. Cropping keeps the frame full and preserves the vertical space your hook text occupies; padding is the right call when the edges of the frame carry information like a full product shot or on-screen pricing.
Can I automate TikTok ad creative in n8n without installing FFmpeg?
Yes. An HTTP Request node pointed at a video API replaces the local binary entirely, which is the only option on n8n Cloud since the Execute Command node was removed. The workflow submits one job per hook-and-ratio combination and polls for each outputUrl.
Does burning a hook into the video cause a review rejection?
Burned-in text is fine on TikTok and Meta as long as it stays out of the UI safe zones and follows each platform's claim rules. Amazon is the exception worth planning around: it rejects third-party branding and logo-first openings on listing videos, so ship the marketplace cut from the clean master.
How long does a 12-variant batch take to render?
Twelve single-pass renders of a 15-second 1080p clip is 12 independent jobs, and because nothing depends on anything else they all run at once rather than in sequence. That parallelism is the actual saving versus a local machine, where 12 libx264 encodes queue behind each other.
Point the workflow at your master clip, and your hook-by-placement matrix comes back as 12 download URLs while you are still writing the ad copy. Sign up free and run the first matrix on the free tier before October locks your creative.
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.
You might also like

Meta Ads Video Specs: All Four Ratios From One Master File
Meta ads video specs, decoded: the four ratios that actually deliver, the safe-margin math, and one FFmpeg pass that renders 4:5, 9:16, 1:1, and 16:9.

How to use FFmpeg in n8n without touching your Docker image
Most FFmpeg n8n Docker recipes break on n8n 2.x. Compare three working paths: the runners image plus fonts, a static-ffmpeg copy, and an HTTP call on Cloud.

How to Add Chapters to MP4 with FFmpeg (FFMETADATA)
To make FFmpeg add chapters you write an FFMETADATA file and remux with -c copy. The hard part is generating that file: TIMEBASE, escaping, and the last END.
Skip the command line
The Text Hook Variants blueprint runs the same job for you: upload, done.
Run it (free)