Skip to content
Gary Wu
Go back

Content Rewards Clipping Guide

Edit page

Org Status: 🟑 Dormant Cloudflare: N/A Last Audited: 2026-04-28


Content Rewards is a pay-per-view marketplace where brands fund campaigns and creators (called β€œclippers”) earn $1-2.50 per 1,000 views by posting short-form video content on TikTok, Instagram Reels, and YouTube Shorts. Unlike traditional influencer marketing, you don’t need a following, a product, or even a camera. You clip, post, submit, and get paid based on actual views.

This guide covers the business model end-to-end β€” from your first $50 payout to building an automated pipeline that produces and posts AI-generated UGC at scale. The target audience is developers and indie hackers who can write code, not just drag-and-drop editors.

What you’ll learn:


  1. The Problem: Why Traditional Creator Monetization Is Broken
  2. What Is Content Rewards?
  3. The Clipping Business Model
  4. How It Works: Step by Step
  5. Content Formats That Work
  6. Real Earnings Data
  7. The AI Automation Pipeline
  8. Script Generation with LLMs
  9. AI UGC Avatars
  10. Text-to-Speech: Paid vs. Open Source
  11. Stock Footage APIs
  12. Video Composition: FFmpeg, Remotion, and Creatomate
  13. Auto-Posting Pipeline Architecture
  14. Local GPU Rendering
  15. Using Content Rewards as a Brand
  16. Cost Analysis: Manual vs. AI-Assisted vs. Fully Automated
  17. Platform Comparison: Content Rewards vs. Everything Else
  18. Anti-Patterns
  19. The Scaling Playbook
  20. References

The creator economy has a structural problem. Every major monetization path requires you to build an audience first, then figure out how to extract revenue from it. This creates a chicken-and-egg problem that kills most aspiring creators before they earn their first dollar.

The Audience-First Trap

TikTok Creator Rewards Program requires 10,000 followers and 100,000 views in the last 30 days just to apply. Even then, it pays $0.40-$1.00 per 1,000 views β€” and only for original videos over 1 minute long. Most creators spend 6-12 months building to eligibility while earning exactly $0.

YouTube AdSense requires 1,000 subscribers and 4,000 watch hours. The average RPM is $3-15 per 1,000 views, but reaching the threshold takes most channels 12-18 months of consistent posting. During that entire period: $0.

Instagram Reels pays $0.07-$0.50 per 1,000 views through its bonus program β€” when it’s even available. The program is invite-only and has been paused and restarted multiple times.

Freelance UGC creation pays $150-2,000 per video, but requires a portfolio, client relationships, invoicing, revisions, and creative direction calls. It’s a service business with all the overhead that implies.

What Changes If You Get This Right

Content Rewards inverts the model. Brands pre-fund campaigns. You create content and post it. Views are tracked automatically. Payouts happen instantly. No follower count required. No invoicing. No client management. No waiting months to qualify.

The question becomes: can you make content that gets views? If yes, you get paid. If no, you iterate.

Key insight: Content Rewards decouples earning from audience-building. You don’t need followers β€” you need views. And views are a function of content quality and volume, both of which are automatable.


Content Rewards is a performance-based marketplace built on the Whop platform. It connects brands who want organic reach with creators who can produce and distribute short-form video content.

The Core Loop

Brand funds campaign ($1K-$50K budget)
    β†’ Sets payout rate ($1-5 per 1K views)
    β†’ Defines content guidelines
    β†’ Creators browse and join campaigns
    β†’ Creators produce content (clips, UGC, reactions)
    β†’ Creators post on TikTok / IG / YouTube
    β†’ Creators submit post URL to Content Rewards
    β†’ Platform verifies views hourly
    β†’ Creators get paid automatically
    β†’ Brand gets organic reach at $1-5 CPM vs. $25+ CPM for ads

Key Platform Details

DetailValue
Platform fee7% of clipper payouts
Payout frequencyWeekly (every 7 days)
Payout methodInstant via Whop (Stripe)
Minimum payoutVaries by campaign
Submission limitUnlimited per campaign
Follower requirementNone
Account requirementFree Whop account
Content ownershipCreator retains ownership
Geographic restrictionsSome campaigns restrict view geography (e.g., US/UK/CA/AU/NZ only)

How It Differs from Traditional Influencer Marketing

Traditional influencer deals: Brand pays $5,000 flat fee to creator with 100K followers. Creator posts one video. Maybe it gets 50K views ($100 CPM). Maybe it gets 500K views ($10 CPM). Brand takes all the risk.

Content Rewards: Brand deposits $5,000 into campaign at $2/1K views. 200 creators each post 3 videos. The ones that get views earn money. The ones that don’t cost the brand nothing. Brand gets exactly 2,500,000 views for their $5,000 ($2 CPM). Risk shifts to creators.

Key insight: Content Rewards is arbitrage. Brands pay $1-5 per 1K views for organic-looking content. Facebook/Instagram ads cost $25+ per 1K impressions. The 5-25x cost advantage is what funds the entire ecosystem.


Why It Works

The clipping model is attractive because it strips away nearly every barrier to entry in the creator economy:

No product needed. You’re promoting someone else’s brand/product/content. You never handle inventory, customer service, or refunds.

No audience needed. Content Rewards doesn’t require follower counts. A brand-new TikTok account with 0 followers can earn if the content gets views.

No invoicing. Payments are automated through the platform. You never send an invoice, chase a payment, or negotiate a rate.

No creative direction calls. Campaign briefs are self-serve. You read the requirements, create content, submit. No Zoom calls.

No exclusivity. You can work multiple campaigns simultaneously. One video might serve 1 campaign, but you can have 50 campaigns running at once across different brands.

The Unit Economics

A single video takes 15-60 minutes to produce manually (depending on format). At a $2/1K views payout rate:

ViewsEarningsTime to Produce (Manual)Effective Hourly Rate
1,000$230 min$4/hr
10,000$2030 min$40/hr
100,000$20030 min$400/hr
1,000,000$2,00030 min$4,000/hr

The variance is extreme. Most videos get 1,000-10,000 views. A few go viral. The business model works because:

  1. Cost per video approaches zero with automation β€” AI can produce videos for $0.10-$2.00 each
  2. Volume compensates for variance β€” 100 videos at 5,000 average views = 500K views = $1,000 at $2 CPM
  3. Long tail earnings β€” Videos continue earning views (and money) for weeks after posting

Revenue Math at Scale

Assumptions:
- 10 videos/day (automated pipeline)
- Average 5,000 views per video
- $2/1K views payout rate
- 30 days/month

Daily: 10 videos Γ— 5,000 views Γ— $2/1K = $100/day
Monthly: $100 Γ— 30 = $3,000/month
Annual: $3,000 Γ— 12 = $36,000/year

Cost (automated):
- AI avatar/TTS: ~$1/video = $300/month
- Stock footage APIs: Free (Pexels/Pixabay)
- Compute: $0 (local GPU) or ~$50/month (cloud)
- Total: ~$350/month

Net profit: ~$2,650/month = ~$31,800/year

This is the β€œrealistic automation” scenario. Not the $45K/36-hours outlier. Not the $50/month beginner. The achievable middle ground for someone who builds the pipeline.


Step 1: Create a Whop Account

Go to whop.com and sign up for a free account. You’ll need:

Step 2: Browse Active Campaigns

Navigate to Content Rewards Discovery or the Whop Content Rewards hub. You’ll see active campaigns with:

Key insight: Only join campaigns with at least 60% of their budget remaining. If a campaign is 90%+ drained, you risk posting content that earns views but doesn’t get paid because the budget ran out.

Step 3: Read the Campaign Brief Carefully

Every campaign has specific requirements. Common ones include:

Missing a single requirement can get your submission rejected, wasting the views you already generated.

Step 4: Warm Up New Accounts (Critical)

If you’re posting from new social media accounts, spend 3-4 days acting like a normal user before posting campaign content:

Day 1-2: Scroll, like, comment on 20-30 videos in your niche
Day 3: Follow 10-20 accounts in your niche
Day 4: Post your first video

Why: Platforms detect and suppress new accounts that immediately
start posting promotional content. The "warmup" period signals
to the algorithm that you're a real user.

This is not optional. New accounts that skip warmup consistently get shadowbanned, meaning your videos get shown to almost nobody.

Step 5: Create Your Content

Choose a format (see Content Formats That Work). Create the video using any combination of:

Step 6: Post and Submit Within 1 Hour

This is the most critical timing requirement:

  1. Post the video on TikTok/Instagram/YouTube
  2. Copy the post URL
  3. Go to the Content Rewards campaign page
  4. Click β€œSubmit”
  5. Paste the URL and upload the media file
  6. Submit

You only get paid for views that happen after submission. If your video goes viral before you submit, those views don’t count. Submit within 1 hour of posting.

Step 7: Track and Collect

The platform checks views hourly. Payouts are processed weekly. You can track earnings in real-time on the Whop dashboard.

Payout calculation example:
- Payout rate: $2/1K views
- Your video gets 50,000 views
- Campaign max payout per clip: $3,000
- Your earnings: min(50,000 Γ— $2/1,000, $3,000) = $100

Another example (viral clip):
- Payout rate: $2/1K views
- Your video gets 2,000,000 views
- Campaign max payout per clip: $3,000
- Your earnings: min(2,000,000 Γ— $2/1,000, $3,000) = $3,000 (capped)

Step 8: Iterate and Scale

Your first few videos will probably underperform. That’s normal. The iteration loop:

  1. Post 5-10 videos across different formats
  2. Check which ones get traction (1,000+ views in 24 hours)
  3. Double down on the winning format
  4. Increase volume on what works
  5. Automate production of the winning format

Not all content formats perform equally. Here they are ranked by effort-to-earnings ratio, from lowest effort to highest production value.

Format 1: Faceless Slideshows (Lowest Effort)

What it is: A series of images/screenshots with text overlay, background music, and optional voiceover. No camera, no face, no editing skill required.

Example topics:

Production time: 5-15 minutes (manual), < 1 minute (automated)

Typical performance: 1K-50K views. Low ceiling but extremely high volume potential.

Tools needed:
- CapCut (free) or Canva (free tier)
- Stock images from Pexels/Unsplash
- Background music (TikTok library or royalty-free)
- Text: bold, centered, large font

Why it works: TikTok’s algorithm rewards watch time and completion rate. Short slideshows (15-30 seconds) with curiosity-driven text get high completion rates. The algorithm doesn’t care if you showed your face.

Format 2: Screen Recording + Voiceover

What it is: Record your screen showing a product, tool, or process. Add voiceover explaining what’s happening.

Example topics:

Production time: 15-30 minutes (manual), 5-10 minutes (semi-automated)

Typical performance: 5K-200K views. Higher ceiling because it demonstrates real product usage.

Tools needed:
- Screen recorder (OBS free, or QuickTime on Mac)
- Audio editor or TTS for voiceover
- CapCut for assembly + captions

Format 3: β€œDid You Know” / Facts Format

What it is: Quick-fire facts or surprising information about a topic, presented with kinetic text, stock footage, and an engaging voiceover.

Example topics:

Production time: 20-40 minutes (manual), 2-5 minutes (automated)

Typical performance: 10K-500K views. This format has the highest viral potential for faceless content.

Pipeline:
1. LLM generates 5 "hook + fact + CTA" scripts
2. TTS converts script to voiceover
3. Stock footage matched to keywords
4. FFmpeg composites video + audio + text overlay
5. Output: 5 ready-to-post videos

Format 4: Reaction Videos

What it is: Record yourself reacting to the brand’s existing content. Show genuine surprise, interest, or commentary.

Production time: 15-30 minutes per video

Typical performance: 10K-1M+ views. Human faces dramatically increase engagement.

Limitation: Cannot be fully automated (requires a real human reacting). Can be semi-automated with templates, auto-captions, and batch processing.

Format 5: AI UGC Avatars (Highest Automation Potential)

What it is: An AI-generated human avatar delivers a scripted message about the brand’s product. Looks like a real person talking to camera.

Production time: 2-5 minutes per video (once pipeline is built)

Typical performance: 5K-100K views. Performance depends heavily on avatar quality.

Critical caveat: Some campaigns explicitly ban AI-generated content. Read the brief before using this format.

Pipeline:
1. LLM generates script
2. AI avatar service renders video (HeyGen, Arcads, Creatify)
3. Add captions + background music
4. Post and submit

Format Comparison

FormatEffortAutomationAvg. ViewsScalabilityCampaign Restrictions
Faceless SlideshowVery LowFull1K-50KExcellentRarely restricted
Screen RecordingLowPartial5K-200KGoodRarely restricted
”Did You Know”MediumFull10K-500KExcellentRarely restricted
Reaction VideoMediumNone10K-1M+PoorNever restricted
AI UGC AvatarLowFull5K-100KExcellentSometimes banned

Key insight: The β€œDid You Know” format hits the sweet spot of high automation potential, high average views, and few campaign restrictions. It’s the format most suited to the developer/automation approach.


Let’s be honest about what people actually earn. The outliers get all the attention. The median earner makes far less.

The Outliers (Top 0.1%)

@jessieclipping β€” Reported earning $45K in 36 hours from Content Rewards. This tweet received 15,489 bookmarks, making it one of the most-saved clipping posts on X. This is not a realistic benchmark. This is the equivalent of winning a lottery ticket β€” it happened because multiple videos went massively viral simultaneously during a high-budget campaign.

In a more instructive post, @jessieclipping reported earning $340 in her first month β€” a much more realistic entry point.

@reyaffrev β€” Reports earning $50K+ total at age 17, with $1,600/week from reposting clips. This represents consistent high performance over months, not a single viral moment. Still top 1% territory.

Agency model β€” The clippa.net guide documents an agency owner with $664,000 in lifetime Stripe revenue from managing multiple clipping accounts and campaigns.

The Realistic Middle (Top 10-30%)

Based on aggregated data from multiple sources:

TimelineRealistic EarningsAssumptions
Month 1$50-2002-3 videos/day, learning what works
Month 2$200-5005 videos/day, one format dialed in
Month 3$500-1,00010 videos/day, multi-platform posting
Month 6$1,000-3,000Automated pipeline, 15+ videos/day
Year 1$3,000-10,000/monthFull automation, multiple campaigns

Source: The Complete Whop Clipping Guide

The Median Earner (Most People)

Most clippers earn $50-200/month. That’s $600-$2,400/year from a side hustle with zero startup costs. Not life-changing, but not nothing.

The clippers who stay at this level typically:

Campaign-Level Data

Lovable AI campaign:

Hostage Tape campaign:

Lil Baby music campaign:

High-CPM campaigns:

The Honest Take

Earnings distribution (estimated):

Bottom 50%:  $0 - $100/month   (gave up or post < 5 videos/week)
50th-75th:   $100 - $500/month (consistent posting, one platform)
75th-90th:   $500 - $2,000/month (multi-platform, 5+ videos/day)
90th-95th:   $2,000 - $5,000/month (automation + multiple campaigns)
95th-99th:   $5,000 - $20,000/month (agency model or viral consistency)
Top 1%:      $20,000+/month (viral outliers, huge scale)

The people making $10K+/month aren’t hoping for one video to blow up. They’re posting 100+ clips per week, tracking what performs, and systemizing the entire process. Which is exactly what automation enables.


Here’s the architecture for a fully automated content production pipeline. Each component is replaceable β€” use the specific tools that fit your budget and technical skill.

Pipeline Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    CONTENT PRODUCTION PIPELINE                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Campaign β”‚    β”‚  Script  β”‚    β”‚  Media   β”‚    β”‚  Video   β”‚  β”‚
β”‚  β”‚  Scanner  │───▢│Generator │───▢│ Fetcher  │───▢│Compositorβ”‚  β”‚
β”‚  β”‚          β”‚    β”‚  (LLM)   β”‚    β”‚(Pexels/  β”‚    β”‚(FFmpeg/  β”‚  β”‚
β”‚  β”‚          β”‚    β”‚          β”‚    β”‚ Pixabay) β”‚    β”‚Remotion) β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                       β”‚          β”‚
β”‚                                                       β–Ό          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Analytics β”‚    β”‚  Submit  β”‚    β”‚  Auto    β”‚    β”‚  Audio   β”‚  β”‚
β”‚  β”‚ Tracker  │◀───│  to CR   │◀───│ Poster   │◀───│Generator β”‚  β”‚
β”‚  β”‚          β”‚    β”‚          β”‚    β”‚(API/Bot) β”‚    β”‚  (TTS)   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Component Breakdown

ComponentPurposeOptions (Free β†’ Paid)
Campaign ScannerFind high-budget campaignsManual β†’ Whop API scraping
Script GeneratorWrite hooks + scriptsLocal LLM β†’ GPT-4o/Claude
Media FetcherGet stock footage/imagesPexels API (free) β†’ Shutterstock
Audio GeneratorTTS voiceoverPiper/Coqui (free) β†’ ElevenLabs
Video CompositorAssemble final videoFFmpeg (free) β†’ Remotion β†’ Creatomate
Auto PosterPost to TikTok/IG/YTManual β†’ API bots β†’ OpenClaw
SubmissionSubmit URL to Content RewardsManual (required for most)
AnalyticsTrack views/earningsWhop dashboard + custom tracking

The script is the most important part of any video. A great script with mediocre visuals outperforms great visuals with a weak script every time.

Script Structure

Every high-performing short-form video follows this structure:

HOOK (0-3 seconds):
  Provocative statement, question, or visual that stops the scroll.
  "This AI tool just replaced my entire marketing team."

BODY (3-25 seconds):
  2-3 key points delivered rapidly.
  Show the product/tool/concept in action.
  Each point should build on the previous one.

CTA (25-30 seconds):
  Clear next step.
  "Link in bio" / "Follow for more" / "Try it free"

TypeScript Script Generator

import Anthropic from "@anthropic-ai/sdk";

interface VideoScript {
  hook: string;
  body: string[];
  cta: string;
  searchTerms: string[]; // for stock footage matching
  duration: number; // estimated seconds
}

interface CampaignBrief {
  brandName: string;
  productDescription: string;
  targetAudience: string;
  keyBenefits: string[];
  restrictions: string[];
  tone: "casual" | "professional" | "excited" | "educational";
}

async function generateScripts(
  brief: CampaignBrief,
  count: number = 5
): Promise<VideoScript[]> {
  const client = new Anthropic();

  const prompt = `Generate ${count} short-form video scripts for a Content Rewards campaign.

Brand: ${brief.brandName}
Product: ${brief.productDescription}
Target audience: ${brief.targetAudience}
Key benefits: ${brief.keyBenefits.join(", ")}
Restrictions: ${brief.restrictions.join(", ")}
Tone: ${brief.tone}

Each script must:
1. Have a hook that stops the scroll (first 3 seconds)
2. Deliver 2-3 key points in the body (3-25 seconds)
3. End with a clear CTA (25-30 seconds)
4. Total duration: 15-30 seconds
5. Include search terms for matching stock footage

Return as JSON array of objects with fields:
hook, body (string array), cta, searchTerms (string array), duration (number)

Focus on "Did You Know" and educational formats.
Avoid clickbait that doesn't deliver.
Each script should take a different angle on the product.`;

  const response = await client.messages.create({
    model: "claude-sonnet-4-20250514",
    max_tokens: 2000,
    messages: [{ role: "user", content: prompt }],
  });

  const text =
    response.content[0].type === "text" ? response.content[0].text : "";
  const jsonMatch = text.match(/\[[\s\S]*\]/);
  if (!jsonMatch) throw new Error("Failed to parse script response");

  return JSON.parse(jsonMatch[0]) as VideoScript[];
}

// Usage
const scripts = await generateScripts({
  brandName: "Lovable",
  productDescription: "AI-powered web app builder",
  targetAudience: "Indie hackers, non-technical founders",
  keyBenefits: [
    "Build full-stack apps with prompts",
    "No coding required",
    "Ships in minutes not months",
  ],
  restrictions: ["No AI voiceovers", "Must show real product usage"],
  tone: "excited",
});

console.log(`Generated ${scripts.length} scripts`);
for (const script of scripts) {
  console.log(`\nHook: "${script.hook}"`);
  console.log(`Duration: ${script.duration}s`);
  console.log(`Stock footage terms: ${script.searchTerms.join(", ")}`);
}

Prompt Engineering Tips for Video Scripts

const HOOK_FORMULAS = [
  // Curiosity gap
  "This {tool} just {unexpected_result} and nobody is talking about it.",
  // Contrarian take
  "Stop {common_advice}. Here's what actually works.",
  // Social proof
  "{number} people switched to {product} last month. Here's why.",
  // Fear of missing out
  "If you're still {old_way}, you're leaving money on the table.",
  // Direct challenge
  "I bet you didn't know {product} could do this.",
];

const BODY_STRUCTURES = {
  // Problem β†’ Solution β†’ Proof
  problemSolutionProof: [
    "State a pain point the audience feels daily",
    "Show how the product solves it (screen recording or demo)",
    "Show a result/metric/testimonial",
  ],
  // Before β†’ After β†’ How
  beforeAfterHow: [
    "Show the old/manual/painful way",
    "Show the result with the product",
    "Quick walkthrough of the process",
  ],
  // Three reasons
  threeReasons: [
    "Reason 1: The obvious benefit",
    "Reason 2: The unexpected benefit",
    "Reason 3: The emotional/status benefit",
  ],
};

Cost Per Script

ProviderCost per ScriptQualitySpeed
Claude 3.5 Sonnet~$0.003Excellent2-3s
GPT-4o~$0.005Excellent2-3s
GPT-4o-mini~$0.0005Good1-2s
Llama 3 (local)$0Good5-10s
Gemini Flash~$0.001Good1-2s

At $0.003/script, generating 100 scripts/day costs $0.30. Script generation is essentially free.


AI-generated avatars deliver scripted content that looks like a real person talking to camera. Quality has improved dramatically β€” the best tools are nearly indistinguishable from real humans in short-form content.

Tool Comparison

ToolStarting PricePer-Video CostAvatar QualityLip SyncGesturesAPI Available
HeyGen$29/mo~$0.50-2.00ExcellentExcellentYesYes
Arcads$100/mo (10 videos)~$10.00Best-in-classExcellentNaturalYes
Creatify$39/mo~$0.50-1.50Very GoodGoodLimitedYes
MakeUGCCredit-based~$5-10GoodGoodLimitedNo
Synthesia$29/mo~$1-3ExcellentExcellentYesYes

Key insight: Creatify delivers 85-90% of Arcads’ quality at 60-70% of the price. For high-volume clipping, Creatify or HeyGen are the best value. Arcads is for when quality matters more than cost.

HeyGen API Example

interface HeyGenVideoRequest {
  video_inputs: Array<{
    character: {
      type: "avatar";
      avatar_id: string;
      avatar_style: "normal" | "circle" | "closeUp";
    };
    voice: {
      type: "text";
      input_text: string;
      voice_id: string;
      speed?: number;
    };
    background?: {
      type: "color" | "image" | "video";
      value: string;
    };
  }>;
  dimension?: { width: number; height: number };
  aspect_ratio?: "16:9" | "9:16" | "1:1";
}

async function generateAvatarVideo(
  script: string,
  avatarId: string
): Promise<string> {
  const response = await fetch("https://api.heygen.com/v2/video/generate", {
    method: "POST",
    headers: {
      "X-Api-Key": process.env.HEYGEN_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      video_inputs: [
        {
          character: {
            type: "avatar",
            avatar_id: avatarId,
            avatar_style: "normal",
          },
          voice: {
            type: "text",
            input_text: script,
            voice_id: "en-US-JennyNeural",
            speed: 1.1, // slightly faster for short-form
          },
        },
      ],
      aspect_ratio: "9:16", // vertical for TikTok/Reels/Shorts
    } satisfies HeyGenVideoRequest),
  });

  const data = await response.json();
  return data.data.video_id; // poll for completion
}

async function pollVideoStatus(videoId: string): Promise<string> {
  while (true) {
    const res = await fetch(
      `https://api.heygen.com/v1/video_status.get?video_id=${videoId}`,
      { headers: { "X-Api-Key": process.env.HEYGEN_API_KEY! } }
    );
    const data = await res.json();

    if (data.data.status === "completed") {
      return data.data.video_url;
    }
    if (data.data.status === "failed") {
      throw new Error(`Video generation failed: ${data.data.error}`);
    }

    await new Promise((resolve) => setTimeout(resolve, 5000));
  }
}

Important: Campaign Restrictions on AI Content

Many campaigns explicitly ban AI-generated content. The Lovable campaign states β€œNo AI voiceovers or obvious AI content.” Before using AI avatars for any campaign, check the brief.

Campaigns that typically allow AI content:

Campaigns that typically ban AI content:


TTS is the audio backbone of faceless content. You need natural-sounding voiceover that doesn’t trigger the β€œthis is AI” detector in viewers’ brains.

ServiceCostQualityLatencyLanguagesAPI
ElevenLabs$5/mo starter, ~$180/1M charsBest-in-class1-3s29+Yes
OpenAI TTS$15/1M charsVery Good1-2s50+Yes
Google Cloud TTS$4/1M chars (standard)Good<1s40+Yes
Amazon Polly$4/1M charsGood<1s30+Yes

Open Source Options (Free, Run Locally)

ProjectQualitySpeed (GPU)LanguagesVoice Cloning
PiperGoodReal-time+30+No
Coqui TTSVery GoodNear real-time16+Yes (XTTS-v2)
BarkExcellentSlow (10-30s)10+Limited
StyleTTS2ExcellentReal-timeEnglishYes

Key insight: For high-volume clipping, OpenAI TTS at $15/1M characters is the best value. A typical 30-second script is ~500 characters, so $15 buys you 2,000 voiceovers. At 10 videos/day, that’s 200 days of production for $15. ElevenLabs sounds better but costs 12x more.

OpenAI TTS Example

import OpenAI from "openai";
import { writeFile } from "node:fs/promises";

const openai = new OpenAI();

async function generateVoiceover(
  text: string,
  outputPath: string,
  voice: "alloy" | "echo" | "fable" | "onyx" | "nova" | "shimmer" = "nova"
): Promise<void> {
  const response = await openai.audio.speech.create({
    model: "tts-1",
    voice,
    input: text,
    speed: 1.1, // slightly faster for short-form engagement
    response_format: "mp3",
  });

  const buffer = Buffer.from(await response.arrayBuffer());
  await writeFile(outputPath, buffer);
}

// Generate voiceovers for multiple scripts
async function batchVoiceover(
  scripts: Array<{ id: string; text: string }>
): Promise<Map<string, string>> {
  const results = new Map<string, string>();

  // Process in parallel batches of 5
  for (let i = 0; i < scripts.length; i += 5) {
    const batch = scripts.slice(i, i + 5);
    await Promise.all(
      batch.map(async (script) => {
        const path = `/tmp/voiceover-${script.id}.mp3`;
        await generateVoiceover(script.text, path);
        results.set(script.id, path);
      })
    );
  }

  return results;
}

Piper (Free, Local) Example

pip install piper-tts

wget https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/amy/medium/en_US-amy-medium.onnx
wget https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/amy/medium/en_US-amy-medium.onnx.json

echo "Did you know this AI tool can build entire websites from a single prompt?" | \
  piper --model en_US-amy-medium.onnx --output_file voiceover.wav

ffmpeg -i voiceover.wav -codec:a libmp3lame -qscale:a 2 voiceover.mp3

TTS Cost Comparison for 100 Videos/Day

ServiceCost/VideoCost/Day (100)Cost/Month (3,000)
ElevenLabs~$0.09$9.00$270.00
OpenAI TTS~$0.0075$0.75$22.50
Google Cloud TTS~$0.002$0.20$6.00
Piper (local)$0.00$0.00$0.00
Coqui (local)$0.00$0.00$0.00

Assumes 500 characters per script average.


Stock footage gives faceless content visual variety without needing to record anything. Two APIs dominate the free tier.

Pexels API

Pexels offers completely free access to their entire library of photos and videos. Rate-limited to 200 requests/hour and 20,000 requests/month.

interface PexelsVideo {
  id: number;
  width: number;
  height: number;
  duration: number;
  url: string;
  video_files: Array<{
    id: number;
    quality: "hd" | "sd" | "uhd";
    file_type: string;
    width: number;
    height: number;
    fps: number;
    link: string;
  }>;
}

interface PexelsSearchResponse {
  page: number;
  per_page: number;
  total_results: number;
  videos: PexelsVideo[];
}

async function searchPexelsVideos(
  query: string,
  options: {
    perPage?: number;
    orientation?: "landscape" | "portrait" | "square";
    minDuration?: number;
    maxDuration?: number;
  } = {}
): Promise<PexelsVideo[]> {
  const params = new URLSearchParams({
    query,
    per_page: String(options.perPage ?? 10),
    orientation: options.orientation ?? "portrait", // 9:16 for TikTok
  });

  if (options.minDuration) {
    params.set("min_duration", String(options.minDuration));
  }
  if (options.maxDuration) {
    params.set("max_duration", String(options.maxDuration));
  }

  const response = await fetch(
    `https://api.pexels.com/videos/search?${params}`,
    {
      headers: {
        Authorization: process.env.PEXELS_API_KEY!,
      },
    }
  );

  const data: PexelsSearchResponse = await response.json();
  return data.videos;
}

async function downloadBestQualityVideo(
  video: PexelsVideo,
  outputPath: string
): Promise<void> {
  // Prefer HD portrait video files
  const file =
    video.video_files
      .filter((f) => f.quality === "hd" && f.height > f.width) // portrait
      .sort((a, b) => b.height - a.height)[0] ??
    video.video_files.sort((a, b) => b.height - a.height)[0];

  if (!file) throw new Error(`No video files for video ${video.id}`);

  const response = await fetch(file.link);
  const buffer = Buffer.from(await response.arrayBuffer());
  const { writeFile } = await import("node:fs/promises");
  await writeFile(outputPath, buffer);
}

// Usage: fetch footage matching script search terms
async function fetchFootageForScript(
  searchTerms: string[],
  clipsNeeded: number = 3
): Promise<string[]> {
  const paths: string[] = [];

  for (let i = 0; i < Math.min(searchTerms.length, clipsNeeded); i++) {
    const videos = await searchPexelsVideos(searchTerms[i], {
      perPage: 3,
      orientation: "portrait",
      minDuration: 5,
      maxDuration: 15,
    });

    if (videos.length > 0) {
      const randomIdx = Math.floor(Math.random() * videos.length);
      const path = `/tmp/footage-${i}.mp4`;
      await downloadBestQualityVideo(videos[randomIdx], path);
      paths.push(path);
    }
  }

  return paths;
}

Pixabay API

Pixabay also offers free video access, with a slightly different rate limit structure.

interface PixabayVideo {
  id: number;
  pageURL: string;
  type: string;
  tags: string;
  duration: number;
  videos: {
    large: { url: string; width: number; height: number; size: number };
    medium: { url: string; width: number; height: number; size: number };
    small: { url: string; width: number; height: number; size: number };
    tiny: { url: string; width: number; height: number; size: number };
  };
}

async function searchPixabayVideos(
  query: string,
  perPage: number = 10
): Promise<PixabayVideo[]> {
  const params = new URLSearchParams({
    key: process.env.PIXABAY_API_KEY!,
    q: query,
    video_type: "film",
    per_page: String(perPage),
    safesearch: "true",
  });

  const response = await fetch(
    `https://pixabay.com/api/videos/?${params}`
  );
  const data = await response.json();
  return data.hits as PixabayVideo[];
}

Stock Footage API Comparison

APIFree TierRate LimitVideo QualityPortrait VideosAttribution Required
PexelsUnlimited200/hr, 20K/moUp to 4KManyPhotographer credit appreciated but not required
PixabayUnlimited100/minUp to 1080pLimitedNot required
Unsplash50/hr50/hrPhotos onlyPhotos onlyRequired
ShutterstockPaid onlyPlan-basedUp to 4KManyLicense required
StoryblocksSubscriptionPlan-basedUp to 4KManyNo

Key insight: Pexels is the best free option by far. Good portrait video selection, generous rate limits, high quality, and no attribution required for commercial use. Use Pixabay as a backup when Pexels doesn’t have what you need.


This is where all the components come together. You need to combine stock footage, text overlays, voiceover audio, and captions into a final MP4 file optimized for short-form platforms.

Option 1: FFmpeg (Free, Local, Maximum Control)

FFmpeg is the Swiss Army knife of video processing. Every other video tool uses it under the hood. It’s free, runs locally, and gives you complete control.

Basic Composition: Images + Text + Audio β†’ MP4

#!/bin/bash

INPUT_DIR="./assets"
OUTPUT="output.mp4"
AUDIO="voiceover.mp3"
DURATION=30  # total video duration in seconds

AUDIO_DURATION=$(ffprobe -v error -show_entries format=duration \
  -of default=noprint_wrappers=1:nokey=1 "$AUDIO")

ffmpeg -y \
  -loop 1 -t 10 -i "${INPUT_DIR}/slide1.jpg" \
  -loop 1 -t 10 -i "${INPUT_DIR}/slide2.jpg" \
  -loop 1 -t 10 -i "${INPUT_DIR}/slide3.jpg" \
  -i "$AUDIO" \
  -filter_complex "
    [0:v]scale=1080:1920:force_original_aspect_ratio=decrease,
      pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black,
      drawtext=text='Did you know?':
        fontsize=72:fontcolor=white:
        x=(w-text_w)/2:y=200:
        fontfile=/System/Library/Fonts/Helvetica.ttc:
        enable='between(t,0,3)'[v0];
    [1:v]scale=1080:1920:force_original_aspect_ratio=decrease,
      pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black,
      drawtext=text='This AI tool builds apps':
        fontsize=64:fontcolor=white:
        x=(w-text_w)/2:y=200:
        fontfile=/System/Library/Fonts/Helvetica.ttc[v1];
    [2:v]scale=1080:1920:force_original_aspect_ratio=decrease,
      pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black,
      drawtext=text='Try it free - link in bio':
        fontsize=64:fontcolor=yellow:
        x=(w-text_w)/2:y=200:
        fontfile=/System/Library/Fonts/Helvetica.ttc[v2];
    [v0][v1][v2]concat=n=3:v=1:a=0[outv]
  " \
  -map "[outv]" -map 3:a \
  -c:v libx264 -preset fast -crf 23 \
  -c:a aac -b:a 128k \
  -shortest \
  -movflags +faststart \
  "$OUTPUT"

echo "Created: $OUTPUT"

Advanced: Stock Footage + Animated Captions + Audio

#!/bin/bash

FOOTAGE1="footage-0.mp4"
FOOTAGE2="footage-1.mp4"
FOOTAGE3="footage-2.mp4"
AUDIO="voiceover.mp3"
OUTPUT="final.mp4"

for i in 0 1 2; do
  ffmpeg -y -i "footage-${i}.mp4" \
    -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920" \
    -c:v libx264 -preset fast -crf 23 \
    -an \
    "scaled-${i}.mp4"
done

for i in 0 1 2; do
  ffmpeg -y -i "scaled-${i}.mp4" -t 10 -c copy "trimmed-${i}.mp4"
done

echo "file 'trimmed-0.mp4'" > concat.txt
echo "file 'trimmed-1.mp4'" >> concat.txt
echo "file 'trimmed-2.mp4'" >> concat.txt

ffmpeg -y -f concat -safe 0 -i concat.txt \
  -c copy "combined-footage.mp4"

ffmpeg -y \
  -i "combined-footage.mp4" \
  -i "$AUDIO" \
  -filter_complex "
    [0:v]drawtext=text='This changes everything':
      fontsize=56:fontcolor=white:borderw=3:bordercolor=black:
      x=(w-text_w)/2:y=h-300:
      fontfile=/System/Library/Fonts/Helvetica.ttc:
      enable='between(t,0,5)',
    drawtext=text='Here\\'s how it works':
      fontsize=56:fontcolor=white:borderw=3:bordercolor=black:
      x=(w-text_w)/2:y=h-300:
      fontfile=/System/Library/Fonts/Helvetica.ttc:
      enable='between(t,5,15)',
    drawtext=text='Try it now - link in bio':
      fontsize=56:fontcolor=yellow:borderw=3:bordercolor=black:
      x=(w-text_w)/2:y=h-300:
      fontfile=/System/Library/Fonts/Helvetica.ttc:
      enable='between(t,15,30)'[outv]
  " \
  -map "[outv]" -map 1:a \
  -c:v libx264 -preset fast -crf 23 \
  -c:a aac -b:a 128k \
  -shortest \
  -movflags +faststart \
  "$OUTPUT"

rm -f scaled-*.mp4 trimmed-*.mp4 combined-footage.mp4 concat.txt
echo "Created: $OUTPUT"

TypeScript FFmpeg Wrapper

import { execSync, exec } from "node:child_process";
import { writeFile, unlink } from "node:fs/promises";
import { join } from "node:path";

interface TextOverlay {
  text: string;
  startTime: number;
  endTime: number;
  fontSize?: number;
  color?: string;
  position?: "top" | "center" | "bottom";
}

interface CompositionConfig {
  footageClips: string[]; // paths to video files
  audioPath: string; // path to voiceover
  overlays: TextOverlay[];
  outputPath: string;
  resolution?: { width: number; height: number };
}

function buildDrawTextFilter(overlays: TextOverlay[]): string {
  return overlays
    .map((o) => {
      const y =
        o.position === "top"
          ? "200"
          : o.position === "center"
            ? "(h-text_h)/2"
            : "h-300";
      const fontSize = o.fontSize ?? 56;
      const color = o.color ?? "white";
      const escaped = o.text.replace(/'/g, "\\'").replace(/:/g, "\\:");

      return `drawtext=text='${escaped}':fontsize=${fontSize}:fontcolor=${color}:borderw=3:bordercolor=black:x=(w-text_w)/2:y=${y}:fontfile=/System/Library/Fonts/Helvetica.ttc:enable='between(t,${o.startTime},${o.endTime})'`;
    })
    .join(",");
}

async function composeVideo(config: CompositionConfig): Promise<string> {
  const { width, height } = config.resolution ?? {
    width: 1080,
    height: 1920,
  };
  const tmpDir = "/tmp/compose";
  execSync(`mkdir -p ${tmpDir}`);

  // Scale and trim each footage clip
  const scaledClips: string[] = [];
  const clipDuration = 10; // seconds per clip

  for (let i = 0; i < config.footageClips.length; i++) {
    const scaled = join(tmpDir, `scaled-${i}.mp4`);
    execSync(
      `ffmpeg -y -i "${config.footageClips[i]}" ` +
        `-vf "scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height}" ` +
        `-t ${clipDuration} -c:v libx264 -preset fast -crf 23 -an "${scaled}"`,
      { stdio: "pipe" }
    );
    scaledClips.push(scaled);
  }

  // Create concat file
  const concatFile = join(tmpDir, "concat.txt");
  const concatContent = scaledClips
    .map((p) => `file '${p}'`)
    .join("\n");
  await writeFile(concatFile, concatContent);

  // Concatenate
  const combined = join(tmpDir, "combined.mp4");
  execSync(
    `ffmpeg -y -f concat -safe 0 -i "${concatFile}" -c copy "${combined}"`,
    { stdio: "pipe" }
  );

  // Add overlays + audio
  const drawText = buildDrawTextFilter(config.overlays);
  const filterComplex = drawText
    ? `-filter_complex "[0:v]${drawText}[outv]" -map "[outv]" -map 1:a`
    : `-map 0:v -map 1:a`;

  execSync(
    `ffmpeg -y -i "${combined}" -i "${config.audioPath}" ` +
      `${filterComplex} ` +
      `-c:v libx264 -preset fast -crf 23 ` +
      `-c:a aac -b:a 128k -shortest -movflags +faststart ` +
      `"${config.outputPath}"`,
    { stdio: "pipe" }
  );

  // Cleanup
  for (const f of scaledClips) await unlink(f).catch(() => {});
  await unlink(concatFile).catch(() => {});
  await unlink(combined).catch(() => {});

  return config.outputPath;
}

// Usage
await composeVideo({
  footageClips: [
    "/tmp/footage-0.mp4",
    "/tmp/footage-1.mp4",
    "/tmp/footage-2.mp4",
  ],
  audioPath: "/tmp/voiceover.mp3",
  overlays: [
    {
      text: "Did you know?",
      startTime: 0,
      endTime: 5,
      fontSize: 72,
      color: "white",
      position: "top",
    },
    {
      text: "This AI builds full apps",
      startTime: 5,
      endTime: 15,
      position: "bottom",
    },
    {
      text: "Try it free - link in bio",
      startTime: 15,
      endTime: 30,
      color: "yellow",
      position: "bottom",
    },
  ],
  outputPath: "/tmp/final-video.mp4",
});

Option 2: Remotion (React-Based, Developer-Friendly)

Remotion lets you compose videos using React components. If you’re a TypeScript developer, this is the most natural way to build complex video templates.

// src/UGCVideo.tsx
import { AbsoluteFill, Sequence, Video, Audio, Img } from "remotion";
import { useCurrentFrame, useVideoConfig, interpolate } from "remotion";

interface UGCVideoProps {
  hook: string;
  bodyPoints: string[];
  cta: string;
  footageUrls: string[];
  voiceoverUrl: string;
}

const AnimatedText: React.FC<{
  text: string;
  delay?: number;
}> = ({ text, delay = 0 }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  const opacity = interpolate(
    frame - delay * fps,
    [0, fps * 0.3],
    [0, 1],
    { extrapolateRight: "clamp" }
  );

  const translateY = interpolate(
    frame - delay * fps,
    [0, fps * 0.3],
    [30, 0],
    { extrapolateRight: "clamp" }
  );

  return (
    <div
      style={{
        opacity,
        transform: `translateY(${translateY}px)`,
        fontSize: 56,
        fontWeight: "bold",
        color: "white",
        textShadow: "2px 2px 8px rgba(0,0,0,0.8)",
        textAlign: "center",
        padding: "0 40px",
        lineHeight: 1.3,
      }}
    >
      {text}
    </div>
  );
};

export const UGCVideo: React.FC<UGCVideoProps> = ({
  hook,
  bodyPoints,
  cta,
  footageUrls,
  voiceoverUrl,
}) => {
  const { fps } = useVideoConfig();
  const segmentDuration = 10 * fps; // 10 seconds per segment

  return (
    <AbsoluteFill style={{ backgroundColor: "black" }}>
      {/* Background footage */}
      {footageUrls.map((url, i) => (
        <Sequence
          key={i}
          from={i * segmentDuration}
          durationInFrames={segmentDuration}
        >
          <Video
            src={url}
            style={{
              width: "100%",
              height: "100%",
              objectFit: "cover",
            }}
          />
        </Sequence>
      ))}

      {/* Text overlays */}
      <AbsoluteFill
        style={{
          justifyContent: "flex-end",
          paddingBottom: 300,
        }}
      >
        {/* Hook */}
        <Sequence from={0} durationInFrames={segmentDuration}>
          <AnimatedText text={hook} />
        </Sequence>

        {/* Body points */}
        {bodyPoints.map((point, i) => (
          <Sequence
            key={i}
            from={(i + 1) * segmentDuration}
            durationInFrames={segmentDuration}
          >
            <AnimatedText text={point} delay={0.2} />
          </Sequence>
        ))}

        {/* CTA */}
        <Sequence
          from={(bodyPoints.length + 1) * segmentDuration}
          durationInFrames={segmentDuration}
        >
          <AnimatedText text={cta} />
        </Sequence>
      </AbsoluteFill>

      {/* Voiceover audio */}
      <Audio src={voiceoverUrl} />
    </AbsoluteFill>
  );
};
// render.ts - Render the video programmatically
import { bundle } from "@remotion/bundler";
import { renderMedia, selectComposition } from "@remotion/renderer";
import path from "node:path";

async function renderUGCVideo(props: {
  hook: string;
  bodyPoints: string[];
  cta: string;
  footageUrls: string[];
  voiceoverUrl: string;
  outputPath: string;
}): Promise<string> {
  const bundled = await bundle({
    entryPoint: path.resolve("./src/index.ts"),
    webpackOverride: (config) => config,
  });

  const composition = await selectComposition({
    serveUrl: bundled,
    id: "UGCVideo",
    inputProps: {
      hook: props.hook,
      bodyPoints: props.bodyPoints,
      cta: props.cta,
      footageUrls: props.footageUrls,
      voiceoverUrl: props.voiceoverUrl,
    },
  });

  await renderMedia({
    composition,
    serveUrl: bundled,
    codec: "h264",
    outputLocation: props.outputPath,
    inputProps: {
      hook: props.hook,
      bodyPoints: props.bodyPoints,
      cta: props.cta,
      footageUrls: props.footageUrls,
      voiceoverUrl: props.voiceoverUrl,
    },
  });

  return props.outputPath;
}

Option 3: Creatomate (Cloud API, No Local Rendering)

Creatomate is a cloud video rendering API. You define templates with dynamic inputs, then trigger renders via API. Starting at $54/month for 2,000 credits (~550 videos at 720p, 15s).

interface CreatomateRenderRequest {
  template_id: string;
  modifications: Record<
    string,
    | string
    | { source: string; trim_start?: number; trim_duration?: number }
  >;
  webhook_url?: string;
}

async function renderWithCreatomate(
  templateId: string,
  modifications: Record<string, string>
): Promise<string> {
  const response = await fetch("https://api.creatomate.com/v1/renders", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CREATOMATE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify([
      {
        template_id: templateId,
        modifications,
      },
    ]),
  });

  const renders = await response.json();
  const renderId = renders[0].id;

  // Poll for completion
  while (true) {
    const statusRes = await fetch(
      `https://api.creatomate.com/v1/renders/${renderId}`,
      {
        headers: {
          Authorization: `Bearer ${process.env.CREATOMATE_API_KEY}`,
        },
      }
    );
    const status = await statusRes.json();

    if (status.status === "succeeded") {
      return status.url; // download URL for the rendered video
    }
    if (status.status === "failed") {
      throw new Error(`Render failed: ${status.error_message}`);
    }

    await new Promise((resolve) => setTimeout(resolve, 3000));
  }
}

// Usage with a pre-built template
const videoUrl = await renderWithCreatomate("your-template-id", {
  "hook-text": "Did you know?",
  "body-text": "This AI builds full-stack apps from a single prompt",
  "cta-text": "Try it free β€” link in bio",
  "background-video": "https://example.com/stock-footage.mp4",
  "voiceover-audio": "https://example.com/voiceover.mp3",
});

Composition Tool Comparison

ToolCostSetup TimeFlexibilityRendering SpeedBest For
FFmpegFree2-4 hoursMaximumFast (local GPU)High volume, full control
RemotionFree (OSS)4-8 hoursVery HighMedium (headless Chrome)React developers, complex templates
Creatomate$54-249/mo1-2 hoursMediumFast (cloud)Non-developers, quick start
Shotstack$25-200/mo1-2 hoursMediumFast (cloud)Simple templates
PlainlyCustom2-4 hoursHighFast (cloud)After Effects templates

Key insight: FFmpeg is free and fast but requires shell scripting knowledge. Remotion is the best option for TypeScript developers who want full control. Creatomate is for when you need results fast and don’t want to manage rendering infrastructure.


The final piece: automatically posting generated videos to TikTok, Instagram, and YouTube.

The Hard Truth About Auto-Posting

TikTok has no official posting API for individual creators. You need either:

Instagram allows posting via the Instagram Graph API, but only for business/creator accounts connected to a Facebook Page.

YouTube has the most accessible Data API v3 with full upload support.

Practical Auto-Posting Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  POSTING PIPELINE                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                           β”‚
β”‚  Video Queue (filesystem / database / queue)              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚video1.mp4β”‚  β”‚video2.mp4β”‚  β”‚video3.mp4β”‚  β”‚video4.mp4β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β”‚
β”‚       β”‚              β”‚              β”‚              β”‚        β”‚
β”‚       β–Ό              β–Ό              β–Ό              β–Ό        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚              Scheduler (cron / queue)              β”‚     β”‚
β”‚  β”‚  - Spaces posts 2-4 hours apart                   β”‚     β”‚
β”‚  β”‚  - Varies posting times (not exactly same time)    β”‚     β”‚
β”‚  β”‚  - Respects per-platform daily limits              β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                 β”‚                β”‚                         β”‚
β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚     β”‚   YouTube    β”‚    β”‚  Instagram   β”‚    β”‚  TikTok  β”‚  β”‚
β”‚     β”‚  Data API v3 β”‚    β”‚  Graph API   β”‚    β”‚  Manual  β”‚  β”‚
β”‚     β”‚  (automated) β”‚    β”‚  (automated) β”‚    β”‚  or Bot  β”‚  β”‚
β”‚     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β”‚
β”‚            β”‚                   β”‚                  β”‚         β”‚
β”‚            β–Ό                   β–Ό                  β–Ό         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚           Submission Queue                        β”‚     β”‚
β”‚  β”‚  - Captures post URLs                            β”‚     β”‚
β”‚  β”‚  - Submits to Content Rewards within 1 hour      β”‚     β”‚
β”‚  β”‚  - Tracks submission status                      β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

YouTube Upload Example

import { google } from "googleapis";
import { createReadStream } from "node:fs";

const oauth2Client = new google.auth.OAuth2(
  process.env.YOUTUBE_CLIENT_ID,
  process.env.YOUTUBE_CLIENT_SECRET,
  "http://localhost:3000/oauth/callback"
);

oauth2Client.setCredentials({
  refresh_token: process.env.YOUTUBE_REFRESH_TOKEN,
});

const youtube = google.youtube({ version: "v3", auth: oauth2Client });

interface UploadOptions {
  videoPath: string;
  title: string;
  description: string;
  tags: string[];
  categoryId?: string; // "22" = People & Blogs, "28" = Science & Tech
}

async function uploadToYouTube(options: UploadOptions): Promise<string> {
  const response = await youtube.videos.insert({
    part: ["snippet", "status"],
    requestBody: {
      snippet: {
        title: options.title.slice(0, 100),
        description: options.description,
        tags: options.tags,
        categoryId: options.categoryId ?? "28",
      },
      status: {
        privacyStatus: "public",
        selfDeclaredMadeForKids: false,
      },
    },
    media: {
      body: createReadStream(options.videoPath),
    },
  });

  const videoId = response.data.id;
  return `https://www.youtube.com/shorts/${videoId}`;
}

AI Clipping Agents

Several tools now offer end-to-end automation including auto-posting:

AutoClip β€” Uses Gemini 2.5 Flash to identify viral moments, auto-reframe to 9:16, add captions, and post directly to TikTok, YouTube Shorts, Instagram Reels, and X. Compresses the entire workflow into under 2 minutes.

AutoClips β€” Set up once and auto-post AI videos to 4 platforms daily. Claims 100% hands-free video automation.

OpenClaw β€” Open-source AI assistant (247K GitHub stars) with an autonomous content clipper that uses FFmpeg to trim videos and burn in captions automatically. Creators report producing 5-10x more content without increasing working hours.

OpusClip β€” AI-powered tool that turns long videos into viral short clips. Identifies high-retention moments, adds captions, and optimizes for each platform.


If you’re producing 10+ videos per day, cloud rendering costs add up. A local GPU pays for itself quickly.

GPU Requirements

TaskMinimum GPURecommended GPUNotes
FFmpeg encodingAny (CPU-only works)NVIDIA GTX 1660+NVENC hardware encoding
TTS (Piper)CPU sufficientAnyRuns real-time on CPU
TTS (Coqui XTTS-v2)4GB VRAM8GB+ VRAMVoice cloning needs GPU
TTS (Bark)8GB VRAM12GB+ VRAMSlow even on GPU
AI image generation8GB VRAM12GB+ VRAMSDXL for custom images
Video upscaling8GB VRAM12GB+ VRAMReal-ESRGAN

Budget build ($300-500 used):

Performance build ($500-800 used):

Mac alternative:

FFmpeg with NVIDIA GPU Encoding

ffmpeg -encoders 2>/dev/null | grep nvenc

ffmpeg -y \
  -i input.mp4 \
  -c:v h264_nvenc \
  -preset p4 \
  -cq 23 \
  -c:a aac -b:a 128k \
  -movflags +faststart \
  output.mp4

Cost Comparison: Local vs. Cloud

MetricLocal GPU (RTX 3080)CreatomateAWS MediaConvert
Upfront cost$350 (used)$0$0
Monthly cost~$10 electricity$54-249$50-200
Cost per video~$0.002~$0.10-0.50~$0.05-0.20
100 videos/day$6/month$300-1,500/month$150-600/month
Break-evenMonth 1NeverNever
Latency5-30 seconds30-120 seconds30-60 seconds

Key insight: A used RTX 3080 pays for itself in the first month if you’re producing 50+ videos/day. The combination of FFmpeg NVENC encoding + Piper TTS + Pexels stock footage gives you a zero-marginal-cost video pipeline.


Content Rewards isn’t just for clippers. If you have a product, you can fund a campaign and get 200+ creators making content about your product for $1-5 per 1,000 views.

Why Brands Use It

MetricContent RewardsFacebook/Instagram AdsInfluencer Deal
CPM$1-5$25+$10-100 (variable)
Content typeOrganic UGCPolished ad creativeInfluencer post
Trust factorHigh (looks real)Low (labeled as ad)Medium
Volume200+ videos1-5 ad variants1-3 posts
RiskPay only for viewsPay for impressionsPay upfront
Setup time30 minutesHours (creative + targeting)Days-weeks (outreach)

A startup with a $1,000 budget gets 500,000+ authentic views for the same cost as 40,000 impressions on Instagram Ads.

Setting Up a Brand Campaign

Step 1: Create a Whop account (whop.com)
Step 2: Navigate to Content Rewards for brands
Step 3: Configure your campaign:
  - Budget: $500 - $50,000+
  - Payout rate: $1-5 per 1K views
  - Max payout per clip: $500-3,000
  - Platforms: TikTok, Instagram, YouTube (pick which ones)
  - Content guidelines: What you want, what you don't want
  - Geographic restrictions: Where views should come from
  - Flat fee bonus: Optional per-submission bonus ($5-50)
Step 4: Fund the campaign via Stripe
Step 5: Creators discover and join your campaign
Step 6: Monitor dashboard for submissions, views, spend

Campaign Math

Example: SaaS product launch campaign

Budget: $5,000
Payout rate: $2/1K views
Max per clip: $1,000

Expected results:
- Total views: 2,500,000
- Creators: 100-300
- Videos produced: 300-1,000
- Cost per acquisition (if 0.5% click + 2% convert):
  2,500,000 views Γ— 0.5% click = 12,500 clicks
  12,500 Γ— 2% convert = 250 customers
  $5,000 / 250 = $20 CAC

Compare: Google Ads for SaaS typically $50-200 CAC

The Quality Control Problem

This is the cautionary side. When you open a campaign to the public, you get a mix of quality levels. Brand campaigns commonly report receiving 40+ low-quality submissions alongside a handful of excellent ones.

Common issues:

Mitigation strategies:

Key insight: Content Rewards campaigns work best as a supplement to your marketing, not a replacement. The 80/20 rule applies: 80% of your views will come from 20% of the creators. Budget accordingly.


Here’s what it actually costs to produce content at each level of automation.

Manual Production

Per video:
- Research/ideation: 15 minutes
- Screen recording or filming: 10 minutes
- Editing in CapCut: 20 minutes
- Captions + text overlay: 10 minutes
- Posting to 3 platforms: 5 minutes
- Submitting to Content Rewards: 2 minutes
Total: ~62 minutes

Daily (5 videos): 5+ hours
Monthly (150 videos): 155 hours
Annual (1,800 videos): 1,860 hours

Cost: $0 (just your time)
Effective hourly rate at $500/month earnings: $3.22/hour

AI-Assisted Production

Per video:
- LLM generates script: 30 seconds ($0.003)
- TTS generates voiceover: 30 seconds ($0.008)
- Manual footage selection: 5 minutes
- FFmpeg composition: 1 minute (automated)
- Manual review + post: 5 minutes
- Submit to Content Rewards: 2 minutes
Total: ~14 minutes

Daily (10 videos): 2.3 hours
Monthly (300 videos): 70 hours
Annual (3,600 videos): 840 hours

Cost: ~$3.30/month (AI services)
Effective hourly rate at $1,500/month earnings: $21.38/hour

Fully Automated Production

Per video:
- Script generation: automated ($0.003)
- TTS: automated ($0.008)
- Stock footage fetch: automated ($0.00)
- Video composition: automated ($0.002)
- Auto-posting: automated ($0.00)
- Content Rewards submission: manual* ($0.00)
Total production time: ~30 seconds
Manual time: 2 minutes (submission only)

*Submission to Content Rewards currently requires
 manual URL input. This is the bottleneck.

Daily (20 videos): 40 minutes (submission only)
Monthly (600 videos): 20 hours
Annual (7,200 videos): 240 hours

Cost: ~$8/month (AI services) + $0-50/month (cloud if used)
Effective hourly rate at $3,000/month earnings: $150/hour

Full Cost Comparison Table

CategoryManualAI-AssistedFully Automated
Videos/day51020
Videos/month150300600
Time/day5 hrs2.3 hrs40 min
Time/month155 hrs70 hrs20 hrs
LLM cost/mo$0$1$2
TTS cost/mo$0$2.40$4.80
Stock footage$0$0$0
Video rendering$0$0$0 (local)
Total cost/mo$0$3.40$6.80
Expected earnings$500$1,500$3,000
Net monthly$500$1,497$2,993
Effective $/hr$3.22$21.38$149.65
Setup time0 hrs4-8 hrs20-40 hrs
Technical skillNoneBasicIntermediate

Key insight: The jump from manual to AI-assisted is the highest-ROI change. It cuts time by 55% and triples output. The jump from AI-assisted to fully automated requires more upfront engineering but reduces ongoing time by 71%.


Monetization Platform Comparison

PlatformEarnings per 1K ViewsFollower RequirementContent OwnershipPayout SpeedBarrier to Entry
Content Rewards$1-5NoneCreatorWeekly (instant approval)Very Low
TikTok Creator Rewards$0.40-1.0010K followers + 100K views/30dPlatform licenseMonthlyHigh
YouTube AdSense$3-15 (RPM)1K subs + 4K watch hoursCreatorMonthly (21-day hold)Very High
Instagram Reels Bonus$0.07-0.5010K followersPlatform licenseMonthlyHigh
Freelance UGC$150-2,000/video flatPortfolio requiredNegotiatedNet 30Medium
Affiliate MarketingVariable (CPA-based)None (but need traffic)CreatorMonthlyMedium

Clipping Platform Comparison

PlatformModelBest ForCommission/Fee
Content Rewards (Whop)Pay-per-viewUGC + brand campaigns7% platform fee
ClipPay-per-viewSimilar to Content RewardsVaries
ToprBrand-creator marketplaceDirect brand dealsNegotiated
ClipRewardConnect editors with creatorsEditing servicesService fee
OpusClipAI clipping toolContent generationSubscription
Clipping.netAgency marketplaceAgency-scale operationsVaries

When to Use What

ScenarioBest PlatformWhy
Zero followers, want income nowContent RewardsNo follower requirement
10K+ TikTok followersContent Rewards + TikTok Creator RewardsStack both income streams
1K+ YouTube subscribersContent Rewards + YouTube AdSenseStack both income streams
Building a personal brandYouTube long-form + Content Rewards for cash flowYouTube builds equity, CR pays the bills
Running a SaaS productContent Rewards (as brand)Cheapest organic reach
Agency modelContent Rewards + direct clientsScale via managing multiple clippers

Don’tDo InsteadWhy
Submit before postingPost first, then submit within 1 hourViews only count after submission
Use AI avatars on β€œno AI” campaignsRead the brief, respect restrictionsRejections waste your views
Post from brand-new accountsWarm up accounts for 3-4 days firstNew accounts get shadowbanned
Join campaigns with <20% budget remainingOnly join campaigns with 60%+ budgetLow-budget campaigns might not pay out
Post the same video to multiple campaignsCreate unique content per campaignPlatforms detect and suppress duplicates
Focus on one platform onlyCross-post to TikTok + IG + YouTube3x the views from the same content effort
Chase viral hitsFocus on volume and consistency100 videos at 5K views > waiting for 1 viral hit
Use copyrighted musicUse platform music libraries or royalty-freeCopyright strikes kill accounts
Skip captionsAlways add captions80%+ of social video is watched on mute
Post at random timesPost during peak hours (7-9am, 12-2pm, 7-10pm local)Algorithm rewards early engagement
Ignore analyticsTrack which formats/hooks/topics get viewsData-driven iteration beats guessing
Build a massive pipeline before testingStart with 5-10 manual videos to learn what worksAutomating the wrong format wastes engineering time
Use the most expensive AI tools firstStart with free/cheap options, upgrade when profitablePiper + Pexels + FFmpeg = $0 production cost
Assume $45K/36hrs is normalBudget for $200-500/month initiallyOutlier results are not benchmarks

Phase 1: Manual Learning (Week 1-2)

Goal: Post 5-10 videos manually. Learn what gets views.

Actions:
- Join 3-5 campaigns with good budgets ($2+ CPM)
- Try all 5 content formats
- Post on TikTok + Instagram + YouTube
- Track views per format per platform
- Total investment: $0, ~10 hours

Expected outcome: $50-200 in earnings
Key learning: Which format works for which campaign type

Phase 2: AI-Assisted Production (Week 3-4)

Goal: 10x your output using LLM scripts and TTS voiceover.

Actions:
- Set up script generation (see Script Generator section)
- Set up TTS pipeline (OpenAI TTS or Piper)
- Batch-produce 10 videos/day
- Continue manual posting + submission
- Total investment: ~$5/month, ~8 hours setup

Expected outcome: $300-800/month
Key learning: Production bottleneck is now posting, not creating

Phase 3: Semi-Automated Pipeline (Month 2-3)

Goal: Automate everything except posting and submission.

Actions:
- Build FFmpeg composition pipeline
- Integrate Pexels API for stock footage
- Create 3-5 video templates (different formats)
- Set up cron job to produce videos overnight
- Manual posting in morning batch (30-60 min/day)
- Total investment: ~$10/month, 20-40 hours engineering

Expected outcome: $1,000-2,000/month
Key learning: Template quality matters more than volume

Phase 4: Full Automation (Month 3-6)

Goal: Minimize daily manual time to <30 minutes.

Actions:
- Add auto-posting for YouTube (API) and Instagram (Graph API)
- TikTok remains semi-manual (use scheduling tools)
- Automated campaign scanning for new high-budget campaigns
- Analytics dashboard tracking per-video ROI
- A/B testing frameworks for hooks and formats
- Total investment: ~$50/month (tools), 40-80 hours engineering

Expected outcome: $2,000-5,000/month
Key learning: The bottleneck shifts to campaign selection and content quality

Phase 5: Agency Scale (Month 6+)

Goal: Manage multiple accounts and campaigns.

Actions:
- Hire 1-2 VAs for posting/submission ($3-5/hr)
- Run 10+ campaigns simultaneously
- Build template library (20+ templates)
- Offer campaign management to brands (20-30% fee)
- Total investment: $500-1,000/month (VAs + tools)

Expected outcome: $5,000-20,000/month
Key learning: Operations management becomes the job, not content creation

Here’s everything wired together β€” a single TypeScript script that takes a campaign brief and produces a ready-to-post video.

import Anthropic from "@anthropic-ai/sdk";
import OpenAI from "openai";
import { execSync } from "node:child_process";
import { writeFile, mkdir, unlink } from "node:fs/promises";
import { join } from "node:path";

// ── Types ──────────────────────────────────────────────────

interface CampaignBrief {
  brandName: string;
  productDescription: string;
  targetAudience: string;
  keyBenefits: string[];
  restrictions: string[];
  tone: "casual" | "professional" | "excited" | "educational";
  payoutRate: number; // $/1K views
}

interface VideoScript {
  hook: string;
  body: string[];
  cta: string;
  searchTerms: string[];
  fullNarration: string;
}

interface PexelsVideo {
  id: number;
  video_files: Array<{
    quality: string;
    width: number;
    height: number;
    link: string;
  }>;
}

// ── Step 1: Generate Script ────────────────────────────────

async function generateScript(
  brief: CampaignBrief
): Promise<VideoScript> {
  const client = new Anthropic();

  const response = await client.messages.create({
    model: "claude-sonnet-4-20250514",
    max_tokens: 1000,
    messages: [
      {
        role: "user",
        content: `Generate ONE short-form video script (30 seconds) for ${brief.brandName}.
Product: ${brief.productDescription}
Audience: ${brief.targetAudience}
Benefits: ${brief.keyBenefits.join(", ")}
Restrictions: ${brief.restrictions.join(", ")}
Tone: ${brief.tone}

Return JSON: { hook, body: string[], cta, searchTerms: string[], fullNarration: string }
fullNarration = the complete spoken script (hook + body + cta as one flowing text).`,
      },
    ],
  });

  const text =
    response.content[0].type === "text" ? response.content[0].text : "";
  const match = text.match(/\{[\s\S]*\}/);
  if (!match) throw new Error("Failed to parse script");
  return JSON.parse(match[0]);
}

// ── Step 2: Generate Voiceover ─────────────────────────────

async function generateVoiceover(
  text: string,
  outputPath: string
): Promise<void> {
  const openai = new OpenAI();
  const response = await openai.audio.speech.create({
    model: "tts-1",
    voice: "nova",
    input: text,
    speed: 1.1,
    response_format: "mp3",
  });
  const buffer = Buffer.from(await response.arrayBuffer());
  await writeFile(outputPath, buffer);
}

// ── Step 3: Fetch Stock Footage ────────────────────────────

async function fetchFootage(
  searchTerms: string[],
  outputDir: string
): Promise<string[]> {
  const paths: string[] = [];

  for (let i = 0; i < Math.min(searchTerms.length, 3); i++) {
    const params = new URLSearchParams({
      query: searchTerms[i],
      per_page: "5",
      orientation: "portrait",
      min_duration: "5",
      max_duration: "15",
    });

    const res = await fetch(
      `https://api.pexels.com/videos/search?${params}`,
      { headers: { Authorization: process.env.PEXELS_API_KEY! } }
    );
    const data = await res.json();
    const videos: PexelsVideo[] = data.videos ?? [];

    if (videos.length > 0) {
      const video = videos[Math.floor(Math.random() * videos.length)];
      const file =
        video.video_files.find(
          (f) => f.quality === "hd" && f.height > f.width
        ) ?? video.video_files[0];

      if (file) {
        const videoRes = await fetch(file.link);
        const buf = Buffer.from(await videoRes.arrayBuffer());
        const path = join(outputDir, `footage-${i}.mp4`);
        await writeFile(path, buf);
        paths.push(path);
      }
    }
  }

  return paths;
}

// ── Step 4: Compose Video ──────────────────────────────────

function composeVideo(
  footagePaths: string[],
  audioPath: string,
  script: VideoScript,
  outputPath: string
): void {
  const tmpDir = "/tmp/compose-pipeline";
  execSync(`mkdir -p ${tmpDir}`);

  // Scale and trim clips
  const scaled: string[] = [];
  for (let i = 0; i < footagePaths.length; i++) {
    const out = join(tmpDir, `s-${i}.mp4`);
    execSync(
      `ffmpeg -y -i "${footagePaths[i]}" ` +
        `-vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920" ` +
        `-t 10 -c:v libx264 -preset fast -crf 23 -an "${out}"`,
      { stdio: "pipe" }
    );
    scaled.push(out);
  }

  // Concat
  const concatFile = join(tmpDir, "list.txt");
  const concatTxt = scaled.map((p) => `file '${p}'`).join("\n");
  execSync(`echo '${concatTxt}' > "${concatFile}"`);

  const combined = join(tmpDir, "combined.mp4");
  execSync(
    `ffmpeg -y -f concat -safe 0 -i "${concatFile}" -c copy "${combined}"`,
    { stdio: "pipe" }
  );

  // Add text overlays + audio
  const hookEsc = script.hook.replace(/'/g, "\\'").replace(/:/g, "\\:");
  const ctaEsc = script.cta.replace(/'/g, "\\'").replace(/:/g, "\\:");

  execSync(
    `ffmpeg -y -i "${combined}" -i "${audioPath}" ` +
      `-filter_complex "[0:v]` +
      `drawtext=text='${hookEsc}':fontsize=64:fontcolor=white:borderw=3:bordercolor=black:x=(w-text_w)/2:y=h-350:enable='between(t,0,5)',` +
      `drawtext=text='${ctaEsc}':fontsize=56:fontcolor=yellow:borderw=3:bordercolor=black:x=(w-text_w)/2:y=h-350:enable='between(t,20,30)'` +
      `[outv]" ` +
      `-map "[outv]" -map 1:a ` +
      `-c:v libx264 -preset fast -crf 23 -c:a aac -b:a 128k -shortest -movflags +faststart ` +
      `"${outputPath}"`,
    { stdio: "pipe" }
  );

  // Cleanup
  scaled.forEach((p) => execSync(`rm -f "${p}"`));
  execSync(`rm -f "${concatFile}" "${combined}"`);
}

// ── Main Pipeline ──────────────────────────────────────────

async function produceVideo(brief: CampaignBrief): Promise<string> {
  const workDir = "/tmp/pipeline-" + Date.now();
  await mkdir(workDir, { recursive: true });

  console.log("Step 1: Generating script...");
  const script = await generateScript(brief);
  console.log(`  Hook: "${script.hook}"`);
  console.log(`  Search terms: ${script.searchTerms.join(", ")}`);

  console.log("Step 2: Generating voiceover...");
  const audioPath = join(workDir, "voiceover.mp3");
  await generateVoiceover(script.fullNarration, audioPath);

  console.log("Step 3: Fetching stock footage...");
  const footagePaths = await fetchFootage(script.searchTerms, workDir);
  console.log(`  Found ${footagePaths.length} clips`);

  if (footagePaths.length === 0) {
    throw new Error("No footage found for search terms");
  }

  console.log("Step 4: Composing video...");
  const outputPath = join(workDir, "final.mp4");
  composeVideo(footagePaths, audioPath, script, outputPath);

  console.log(`Done! Video saved to: ${outputPath}`);
  return outputPath;
}

// ── Run ────────────────────────────────────────────────────

const videoPath = await produceVideo({
  brandName: "Lovable",
  productDescription: "AI-powered full-stack web app builder",
  targetAudience: "Indie hackers and non-technical founders",
  keyBenefits: [
    "Build complete web apps with natural language",
    "No coding required",
    "Deploy in minutes",
  ],
  restrictions: ["No AI voiceovers", "Must show real product usage"],
  tone: "excited",
  payoutRate: 2.0,
});

console.log(`\nReady to post: ${videoPath}`);
console.log("Next steps:");
console.log("1. Post to TikTok, Instagram Reels, YouTube Shorts");
console.log("2. Submit URLs to Content Rewards within 1 hour");
console.log("3. Track views and earnings on Whop dashboard");

Running the Pipeline

npm install @anthropic-ai/sdk openai
brew install ffmpeg  # or apt-get install ffmpeg

export ANTHROPIC_API_KEY="sk-..."
export OPENAI_API_KEY="sk-..."
export PEXELS_API_KEY="..."

npx tsx pipeline.ts

Cost Per Video (This Pipeline)

ComponentCost
Claude Sonnet (script)$0.003
OpenAI TTS (voiceover)$0.008
Pexels (footage)$0.00
FFmpeg (rendering)$0.00
Total per video$0.011

At $0.011 per video and a $2/1K views payout, you break even at just 6 views per video. Everything above that is profit.


Yes. Content Rewards is a legitimate marketplace. Brands explicitly authorize you to create and post content about their products. You’re not stealing content β€” you’re creating it under a paid campaign agreement.

”Do I need to show my face?”

No. Faceless formats (slideshows, screen recordings, β€œDid You Know” videos) perform well. Many top clippers never show their face.

”Can I use multiple accounts?”

The platform doesn’t explicitly ban it, but TikTok, Instagram, and YouTube all restrict operating multiple accounts from the same device. Use different devices or be prepared for account flags. This is a gray area β€” proceed at your own risk.

”What happens when a campaign runs out of budget?”

You stop earning from that campaign. Any views generated after the budget depletes aren’t paid. This is why you should check budget remaining before joining.

”Can I do this outside the US?”

Yes. Content Rewards is available globally. However, some campaigns restrict which geographic locations views come from. A campaign targeting US views won’t pay for views from non-US audiences, even if the creator is based elsewhere.

”Is this just reposting other people’s content?”

It can be, but the highest earners create original content. β€œClipping” originally meant cutting highlights from long-form videos (with permission via the campaign). Today, many campaigns want original UGC β€” not just clips of existing content.

”How long do videos keep earning?”

Videos continue earning as long as they get views and the campaign budget hasn’t been depleted. A viral video can earn for weeks. Most videos earn 80% of their total views in the first 48 hours.


Content Rewards clipping is a real business model with real money flowing through it. But like any opportunity, the marketing around it is more optimistic than the median reality.

What’s real:

What’s overhyped:

Who should try this:

Who should skip this:

The automation angle is what makes this interesting for technical people. A non-technical clipper competes on creativity and hustle. A developer competes on scale and efficiency. Both can win, but the developer’s ceiling is higher because marginal cost approaches zero.


Official Platforms and Documentation

Guides and Tutorials

Creator Earnings and Case Studies

AI UGC and Avatar Tools

Text-to-Speech

Stock Footage APIs

Video Composition and Rendering

Auto-Posting and Automation Tools

Creator Earnings Comparisons

Alternative Platforms

Community and Discussion


Edit page
Share this post on:

Previous Post
Durable Object Rate Limiting
Next Post
Reading the Disk in Order