TikTok Ad Creative Automation with FFmpeg API

March 17, 2026 ยท RenderIO

Creative volume wins on TikTok Ads

TikTok's ad platform rewards creative diversity. The algorithm needs multiple creative options to find your best-performing combination of hook, body, and CTA.

Media buyers who test 50+ creatives per week consistently outperform those testing 5-10. But producing 50 unique ad creatives manually costs $2,500-5,000 per week in editing fees.

The math changes when you automate. Five base creatives x 10 variations each = 50 unique ads. FFmpeg handles the variations. RenderIO processes them in parallel.

The variation matrix

From one base creative, you can vary:

  1. Hook text (first 1-3 seconds): 5 different hooks

  2. CTA text (last 2-3 seconds): 3 different CTAs

  3. Color grade: 2-3 different looks

  4. Speed: Normal, 5% faster, 5% slower

5 hooks x 3 CTAs x 2 colors = 30 variations per base creative. With 5 base creatives, that's 150 testable ads.

Creating hook variations

The hook is the text that appears in the first 1-3 seconds. Different hooks capture different audience segments.

# Hook 1: Problem-aware
ffmpeg -i base-creative.mp4 \
  -vf "drawtext=text='Tired of cheap headphones?':fontsize=48:fontcolor=white:borderw=3:bordercolor=black:x=(w-text_w)/2:y=200:enable='between(t,0,3)'" \
  -c:v libx264 -crf 22 -c:a copy \
  hook-problem.mp4

# Hook 2: Curiosity
ffmpeg -i base-creative.mp4 \
  -vf "drawtext=text='This changed how I listen to music':fontsize=44:fontcolor=white:borderw=3:bordercolor=black:x=(w-text_w)/2:y=200:enable='between(t,0,3)'" \
  -c:v libx264 -crf 22 -c:a copy \
  hook-curiosity.mp4

# Hook 3: Social proof
ffmpeg -i base-creative.mp4 \
  -vf "drawtext=text='50K+ people switched to these':fontsize=46:fontcolor=white:borderw=3:bordercolor=black:x=(w-text_w)/2:y=200:enable='between(t,0,3)'" \
  -c:v libx264 -crf 22 -c:a copy \
  hook-social.mp4

# Hook 4: Direct benefit
ffmpeg -i base-creative.mp4 \
  -vf "drawtext=text='40 hours of battery life':fontsize=48:fontcolor=#00FF88:borderw=3:bordercolor=black:x=(w-text_w)/2:y=200:enable='between(t,0,3)'" \
  -c:v libx264 -crf 22 -c:a copy \
  hook-benefit.mp4

# Hook 5: Urgency
ffmpeg -i base-creative.mp4 \
  -vf "drawtext=text='Sale ends tomorrow':fontsize=48:fontcolor=#FF4444:borderw=3:bordercolor=black:x=(w-text_w)/2:y=200:enable='between(t,0,3)'" \
  -c:v libx264 -crf 22 -c:a copy \
  hook-urgency.mp4

Creating CTA variations

# CTA 1: Shop now
ffmpeg -i base.mp4 \
  -vf "drawtext=text='Shop Now - Link in Bio':fontsize=42:fontcolor=white:borderw=3:bordercolor=black:x=(w-text_w)/2:y=h-200:enable='gte(t,12)'" \
  -c:v libx264 -crf 22 -c:a copy \
  cta-shop.mp4

# CTA 2: Limited offer
ffmpeg -i base.mp4 \
  -vf "drawtext=text='60%% OFF Today Only':fontsize=46:fontcolor=#FF6B35:borderw=3:bordercolor=black:x=(w-text_w)/2:y=h-200:enable='gte(t,12)'" \
  -c:v libx264 -crf 22 -c:a copy \
  cta-discount.mp4

# CTA 3: Free shipping
ffmpeg -i base.mp4 \
  -vf "drawtext=text='Free Shipping - Tap to Order':fontsize=40:fontcolor=#00FF88:borderw=3:bordercolor=black:x=(w-text_w)/2:y=h-200:enable='gte(t,12)'" \
  -c:v libx264 -crf 22 -c:a copy \
  cta-shipping.mp4

Combined hook + CTA in one command

ffmpeg -i base-creative.mp4 \
  -vf "\
    drawtext=text='Tired of cheap headphones?':fontsize=48:fontcolor=white:borderw=3:bordercolor=black:x=(w-text_w)/2:y=200:enable='between(t,0,3)',\
    drawtext=text='Shop Now - Link in Bio':fontsize=42:fontcolor=white:borderw=3:bordercolor=black:x=(w-text_w)/2:y=h-200:enable='gte(t,12)'" \
  -c:v libx264 -crf 22 -c:a copy \
  variation-problem-shop.mp4

Batch generate with RenderIO API

const hooks = [
  { id: "problem", text: "Tired of cheap headphones?", color: "white", size: 48 },
  { id: "curiosity", text: "This changed how I listen", color: "white", size: 44 },
  { id: "social", text: "50K+ people switched", color: "white", size: 46 },
  { id: "benefit", text: "40 hours battery life", color: "#00FF88", size: 48 },
  { id: "urgency", text: "Sale ends tomorrow", color: "#FF4444", size: 48 },
];

const ctas = [
  { id: "shop", text: "Shop Now - Link in Bio", color: "white" },
  { id: "discount", text: "60%25 OFF Today Only", color: "#FF6B35" },
  { id: "shipping", text: "Free Shipping - Tap to Order", color: "#00FF88" },
];

const colorGrades = [
  { id: "natural", vf: "" },
  { id: "warm", vf: ",colortemperature=temperature=6500,eq=saturation=1.1" },
];

async function generateAdVariations(baseVideoUrl, productName) {
  const variations = [];

  for (const hook of hooks) {
    for (const cta of ctas) {
      for (const color of colorGrades) {
        const name = `${productName}-${hook.id}-${cta.id}-${color.id}`;
        const hookFilter = `drawtext=text='${hook.text}':fontsize=${hook.size}:fontcolor=${hook.color}:borderw=3:bordercolor=black:x=(w-text_w)/2:y=200:enable='between(t,0,3)'`;
        const ctaFilter = `drawtext=text='${cta.text}':fontsize=42:fontcolor=${cta.color}:borderw=3:bordercolor=black:x=(w-text_w)/2:y=h-200:enable='gte(t,12)'`;
        const vf = `${hookFilter},${ctaFilter}${color.vf}`;

        variations.push({ name, vf });
      }
    }
  }

  // 5 hooks x 3 CTAs x 2 colors = 30 variations
  const jobs = variations.map(v =>
    fetch("https://renderio.dev/api/v1/run-ffmpeg-command", {
      method: "POST",
      headers: {
        "X-API-KEY": process.env.RENDERIO_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ffmpeg_command: `-i {in_video} -vf "${v.vf}" -c:v libx264 -crf 22 -c:a copy -movflags +faststart {out_video}`,
        input_files: { in_video: baseVideoUrl },
        output_files: { out_video: `${v.name}.mp4` },
      }),
    }).then(r => r.json())
  );

  return Promise.all(jobs);
}

// Generate 30 variations from one base creative
const results = await generateAdVariations(
  "https://storage.example.com/base-creative-1.mp4",
  "headphones"
);
console.log(`Created ${results.length} ad variations`);

30 variations per base creative. All processing in parallel.

Scale across multiple creatives

const baseCreatives = [
  { name: "unboxing", url: "https://storage.example.com/unboxing.mp4" },
  { name: "lifestyle", url: "https://storage.example.com/lifestyle.mp4" },
  { name: "comparison", url: "https://storage.example.com/comparison.mp4" },
  { name: "review", url: "https://storage.example.com/review.mp4" },
  { name: "feature", url: "https://storage.example.com/feature.mp4" },
];

async function generateAllVariations() {
  const allJobs = baseCreatives.map(creative =>
    generateAdVariations(creative.url, creative.name)
  );

  const results = await Promise.all(allJobs);
  const totalVariations = results.flat().length;
  console.log(`Created ${totalVariations} total ad variations`);
  // 5 creatives x 30 variations = 150 ads
}

150 unique ad creatives. All from 5 base videos and one script.

Tracking variation performance

Name your output files systematically so you can track which combinations perform:

headphones-problem-shop-natural.mp4
headphones-problem-shop-warm.mp4
headphones-problem-discount-natural.mp4
headphones-curiosity-shop-natural.mp4

When you pull ad performance data, you can analyze:

  • Which hooks get the best view-through rate

  • Which CTAs drive the best click-through rate

  • Which color grades perform per audience segment

This data feeds back into your next batch of creatives.

Weekly ad creative workflow

  1. Monday: Create 5 new base creatives (shoot or generate with AI)

  2. Monday: Run batch generation (150 variations, 5 minutes)

  3. Tuesday: Upload top 50 to TikTok Ads Manager

  4. Wednesday-Friday: Monitor performance

  5. Friday: Analyze data, identify winning hooks/CTAs

  6. Next Monday: New base creatives informed by data

Cost breakdown

ItemWeeklyMonthly
150 ad variations (API calls)150/week600/month
RenderIO Growth plan-$29/mo
Freelance alternative (150 ads)$3,000-7,500$12,000-30,000

At 600 commands per month, the Growth plan at $29/month (1,000 commands) covers your needs. The entire month of creative generation costs less than one freelance ad variation.

Five base creatives. 150 variations. Zero editing time. Test everything.