Skip to content
Gary Wu
Go back

Viral Video Bible

Edit page

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


Every day, faceless finance channels earn $300-$1,000 from videos nobody appeared in, nobody manually edited, and nobody spent more than $0.01 to render. The difference between creators who earn and creators who quit is not talent or equipment β€” it is a system. This guide is that system.

What you will learn:


  1. The Economics of Viral Video
  2. The Trend-First Content Model
  3. The Million-Dollar Script Formula
  4. Visual Language and Production Design
  5. The Render Pipeline
  6. The Hybrid Machine: Shorts Feed Longs
  7. Platform-Specific Optimization
  8. Niche Selection Matrix
  9. Scaling with AI Agents
  10. Monetization Playbook
  11. Compliance and Risk
  12. The First Video Checklist
  13. Comparisons
  14. Anti-Patterns
  15. References

The economics of programmatic video are unlike any other content format. The production cost approaches zero while the revenue scales linearly with views. No inventory. No fulfillment. No customer support. Just views converted to dollars.

Revenue Per Mille (RPM) by Platform

PlatformFormatRPM RangeNotes
YouTube (finance)Long-form (8-15 min)$9-$25Highest RPM of any niche. Q4 peaks at $18-$30+
YouTube (finance)Shorts$0.05-$0.15Low RPM but high volume, funnel to long-form
TikTok Creator Rewards1+ min videos$0.40-$2.00Qualified views only (5s+ watch, FYP, unique)
TikTok Creator Fund (legacy)Any length$0.02-$0.04Nearly worthless, replaced by Creator Rewards
Content RewardsShort-form UGC$1-$2.50Per 1K views, brand campaigns, no audience needed
Instagram ReelsShort-form$0.10-$0.50Inconsistent, better as traffic driver

Cost Per Video

MethodCostQualitySpeed
Local FFmpeg + edge-tts$0.01Medium (robotic voice, basic compositing)30-60 seconds
Local FFmpeg + ElevenLabs$0.15-$0.50High (natural voice, basic compositing)30-90 seconds
Creatomate + ElevenLabs$0.50-$2.00High (natural voice, animations, captions)2-5 minutes
Creatomate + ElevenLabs (long-form)$2.00-$6.00Production (full 10-min video)5-15 minutes
Remotion + ElevenLabs$0.01-$0.50High (React components, custom animations)1-5 minutes
Manual (CapCut/Premiere)$0 (time cost)Highest2-8 hours

The Math That Matters

Scenario: Finance niche, 10 short-form videos per day, 2 long-form per week.

Short-form (TikTok + Content Rewards):
  10 videos/day x 30 days = 300 videos/month
  Average 500 views/video = 150,000 views/month
  TikTok Creator Rewards: 150K views x $0.80 RPM = $120/month
  Content Rewards (top 50 videos): 50K views x $1.50 RPM = $75/month

Long-form (YouTube):
  8 videos/month
  Average 6,250 views/video = 50,000 views/month
  Finance RPM: 50K views x $12 RPM = $600/month

Production cost:
  300 short-form x $0.01 (local FFmpeg) = $3/month
  8 long-form x $2.00 (Creatomate) = $16/month
  ElevenLabs subscription = $22/month
  Total: $41/month

Net: $795/month - $41/month = $754/month profit (95% margin)

At 500K views/month on YouTube alone (achievable within 6-12 months of consistent posting), the math shifts dramatically:

YouTube long-form: 500K views x $15 RPM (finance avg) = $7,500/month
TikTok shorts: 500K views x $0.80 RPM = $400/month
Content Rewards: 100K views x $1.50 RPM = $150/month
Affiliate links: $500-$2,000/month (finance products)
Sponsor integrations: $1,000-$5,000/month

Total: $9,550-$15,050/month
Cost: $100-$200/month (tools + API calls)

Key insight: The unit economics of programmatic video are absurd. A $0.01 video that gets 10,000 views generates $120 in YouTube finance RPM. That is a 1,200,000% return on production cost. The bottleneck is never cost β€” it is distribution and quality.

Revenue Stacking

No single revenue stream hits $10K/month easily. The strategy is to stack:

Layer 1: YouTube AdSense (long-form)        $3,000-$7,500
Layer 2: TikTok Creator Rewards (shorts)    $200-$800
Layer 3: Content Rewards campaigns           $100-$500
Layer 4: Affiliate links (finance products)  $500-$2,000
Layer 5: Sponsor integrations               $500-$5,000
Layer 6: Funnel to owned products           $500-$3,000
         (calculators, tools, courses)
─────────────────────────────────────────────────────────
Total potential:                            $4,800-$18,800/month

Why Asset-First Fails

Most creators start with their content and try to find an audience for it. This is backwards.

Asset-first (broken):
  "I have an LLC cost calculator" β†’ "Let me make a video about LLC costs"
  β†’ Posts to TikTok β†’ 47 views β†’ "TikTok doesn't work"

Trend-first (works):
  "LLC formation is trending on TikTok" β†’ "Let me ride this wave"
  β†’ Creates content matching the trend format β†’ 47,000 views
  β†’ Links to LLC calculator in bio β†’ 200 tool page visits

Nobody searches TikTok for β€œLLC break-even calculator.” They scroll past videos about money, business failures, side hustles, and financial traps. The content that performs is content that matches what people are already watching, with your expertise woven in as the payoff.

The Trend Intelligence Stack

interface TrendSource {
  name: string;
  type: 'api' | 'manual' | 'scrape';
  latency: string;         // How quickly you get signal
  cost: string;
  signalQuality: 'high' | 'medium' | 'low';
}

const trendSources: TrendSource[] = [
  {
    name: 'Virlo API',
    type: 'api',
    latency: 'real-time',
    cost: '$49-$199/month',
    signalQuality: 'high',
    // 2M+ indexed videos, 500K+ hashtags
    // 7-dimension intelligence reports
    // Viral outlier detection
    // Early trend alerts before mainstream saturation
  },
  {
    name: 'TikTok Discover (manual)',
    type: 'manual',
    latency: '1-2 hours',
    cost: 'free',
    signalQuality: 'medium',
    // Check trending sounds, hashtags, formats
    // Note: trends have 24-72 hour windows
  },
  {
    name: 'YouTube Trending API',
    type: 'api',
    latency: 'hourly',
    cost: 'free (quota)',
    signalQuality: 'medium',
    // YouTube Data API v3 trending endpoint
    // Filter by category (finance, education, etc.)
  },
  {
    name: 'Twitter/X Trending',
    type: 'api',
    latency: 'real-time',
    cost: 'API tier dependent',
    signalQuality: 'medium',
    // Cross-platform signal: X trends predict TikTok trends by 12-24 hours
  },
  {
    name: 'Google Trends',
    type: 'api',
    latency: 'daily',
    cost: 'free',
    signalQuality: 'low',
    // Lagging indicator β€” by the time it shows up here, the TikTok wave is half over
  },
];

The magic is not finding trends. The magic is connecting trends to high-RPM niches. Finance content earns 5-10x more per view than entertainment content. Your job is to take whatever is trending and frame it through a finance lens.

interface TrendAngle {
  trend: string;
  nicheConnection: string;
  hookExample: string;
  rpm: string;
}

const angleExamples: TrendAngle[] = [
  {
    trend: 'Celebrity bankruptcy filing',
    nicheConnection: 'LLC asset protection',
    hookExample: '"[Celebrity] lost everything. An LLC would have saved $4.2 million."',
    rpm: '$12-$18',
  },
  {
    trend: 'Tech layoffs wave',
    nicheConnection: 'Side hustle LLC formation',
    hookExample: '"Google laid off 12,000 people. Here is how to never depend on a paycheck again."',
    rpm: '$10-$15',
  },
  {
    trend: 'New tax law passed',
    nicheConnection: 'Direct tax content',
    hookExample: '"This new law saves LLC owners $3,200 per year. Most accountants won\'t tell you."',
    rpm: '$15-$25',
  },
  {
    trend: 'Viral "money saving hack" video',
    nicheConnection: 'Debunk with real numbers',
    hookExample: '"This viral money hack is wrong. Here is the actual math."',
    rpm: '$12-$20',
  },
];

Speed Matters: The Trend Window

Hour 0:   Trend emerges on X/Twitter or TikTok
Hour 6:   Early creators notice it
Hour 12:  First wave of content appears
Hour 24:  Peak engagement window OPENS
Hour 48:  Peak engagement window
Hour 72:  Peak engagement window CLOSES
Hour 96:  Trend is saturated, late entries get minimal views
Hour 168: Trend is dead, evergreen pivot only

Your pipeline must produce a finished video within 2-4 hours of identifying a trend. Manual editing takes 2-8 hours. Programmatic rendering takes 5-30 minutes. The speed advantage is existential.

Key insight: The trend-first model inverts the content creation process. Instead of β€œwhat do I want to say?” the question becomes β€œwhat does the audience want to hear right now, and how does my expertise add value to that conversation?” This is not pandering β€” it is relevance.


Long-Form: The 4-Act Structure

The highest-performing faceless YouTube videos follow a predictable 4-act structure. This is not theory β€” it maps to retention curves across thousands of finance videos.

interface LongFormScript {
  /** Act 1: The Drop (0:00 - 0:30) */
  theDrop: {
    shockingResult: string;   // Start with the outcome, not the setup
    openLoop: string;         // Create a question the viewer MUST answer
    retention: '85-95%';      // Target: lose less than 15% in first 30s
  };
  /** Act 2: The Ascent (0:30 - 3:30) */
  theAscent: {
    origins: string;          // How did we get here?
    successPeriod: string;    // The "everything was great" setup
    dataPoints: string[];     // 3-5 specific numbers
    retention: '65-80%';
  };
  /** Act 3: The Pivot/Crash (3:30 - 7:00) */
  thePivot: {
    hiddenMechanics: string;  // What nobody tells you
    dataVisualization: string; // Charts, comparisons, calculations
    tension: string;          // Build to "wait, what?"
    retention: '55-70%';
  };
  /** Act 4: The Synthesis (7:00 - 8:30) */
  theSynthesis: {
    broaderImplications: string; // What this means for YOU
    closeLoop: string;           // Answer the question from Act 1
    cta: string;                 // Subtle, value-driven
    retention: '45-60%';
  };
}

Example: β€œThe LLC Cost Trap Nobody Talks About” (8 minutes)

ACT 1 β€” THE DROP (0:00 - 0:30)
"Filing an LLC costs $50. Running one costs $4,000. And most of that
money goes to people who do nothing for you. Here's the breakdown
nobody in the LLC industry wants you to see."

[Visual: Dark background. Red "$4,000" flashes. Calculator animation.]

ACT 2 β€” THE ASCENT (0:30 - 3:30)
"When you first hear about LLCs, it sounds perfect. $50 filing fee.
Asset protection. Tax benefits. Every business guru tells you to
form one yesterday. But they always leave out the recurring costs..."

- State filing fee: $50-$500
- Registered agent: $100-$300/year
- EIN filing: free (but services charge $200)
- Operating agreement: free template vs. $2,000 lawyer
- Annual reports: $0-$800/year

[Visual: State-by-state map. Cost breakdown appearing line by line.]

ACT 3 β€” THE PIVOT (3:30 - 7:00)
"But here's where it gets interesting. California charges $800/year
just for the privilege of existing as an LLC. Even if you made zero
dollars. Wyoming charges $50 once. Same legal protection. That's a
$3,200 difference over 4 years for the exact same entity."

[Visual: Side-by-side comparison. California vs. Wyoming. Running total.]

"And the registered agent industry? $300/year for a service that
receives maybe 2 pieces of mail. That's $150 per envelope."

ACT 4 β€” THE SYNTHESIS (7:00 - 8:30)
"The LLC itself isn't expensive. The ecosystem around it is. Pick the
right state. Use free EIN filing. Template operating agreement. And
skip the $300 registered agent if you can use your own address. Total
first-year cost: under $200 instead of $4,000. Link in the description
for the free calculator that runs these numbers for your state."

[Visual: Side-by-side: $4,000 vs. $200. Calculator preview.]

Short-Form: The 60-Second Formula

Short-form scripts follow a compressed version. Every second earns its place.

interface ShortFormScript {
  /** Hook: first 1-3 seconds */
  hook: {
    text: string;          // 3-7 words on screen
    narration: string;     // Shocking stat or provocative claim
    rule: 'Must stop the scroll in under 1 second';
  };
  /** Context: 3-10 seconds */
  trendContext: {
    narration: string;     // Connect to what's trending
    rule: 'Establish relevance to current conversation';
  };
  /** Value: 10-45 seconds */
  valueBomb: {
    points: string[];      // 3-5 rapid-fire facts with specific numbers
    rule: 'Each sentence = one visual beat. No filler.';
  };
  /** CTA: last 3-5 seconds */
  cta: {
    text: string;          // On-screen CTA
    narration: string;     // Engagement driver
    rule: 'Follow, comment, or link-in-bio. Pick one.';
  };
}

Rules for short-form scripts:

  1. Hook in 1 second (the β€œscroll stopper”)
  2. No filler words ever (β€œhey guys”, β€œso basically”, β€œalright so”)
  3. Each sentence = one visual beat (when narration changes, visual changes)
  4. Specific numbers always ($3,200, not β€œthousands”)
  5. No more than 200 words for a 60-second video (speak at 180 WPM)
  6. End with engagement, not information

The LLM Prompt Chain

Script generation is a three-step process, not a single prompt. Each step has a specific role and a specific output format.

Step 1: Fact Retrieval and Structuring

const factRetrievalPrompt = `You are a financial research assistant. Your job is to gather
and structure factual data about a topic for a video script.

TOPIC: {{topic}}
TREND CONTEXT: {{trendContext}}
NICHE: {{niche}}

Output a JSON array of facts. Each fact must include:
- "claim": the factual statement
- "number": the specific number (always use exact figures)
- "source": where this fact comes from
- "surprise_factor": 1-10 (how unexpected is this for the average viewer?)

Return 8-12 facts, sorted by surprise_factor descending.

RULES:
- Every claim must include a specific number
- No approximations ("about", "around", "roughly")
- No opinions β€” only verifiable facts
- Prefer counterintuitive facts (things people assume wrong)

Example output:
[
  {
    "claim": "California charges an $800 annual franchise tax even if LLC revenue is $0",
    "number": "$800",
    "source": "California Franchise Tax Board",
    "surprise_factor": 9
  }
]`;

Step 2: Hook and Open-Loop Generation

const hookGenerationPrompt = `You are a viral video hook specialist. Your job is to write
the first 30 seconds of a video that achieves 85%+ retention.

FACTS (from research step):
{{factsJson}}

TREND CONTEXT: {{trendContext}}
FORMAT: {{format}} (short-form: 3 seconds | long-form: 30 seconds)

For SHORT-FORM, output:
{
  "screen_text": "3-7 word text overlay (ALL CAPS, punchy)",
  "narration": "1-2 sentences that make the viewer NEED to keep watching",
  "open_loop": "The implicit question the viewer must answer"
}

For LONG-FORM, output:
{
  "shocking_result": "Start with the END result β€” the most surprising fact",
  "contrast": "Juxtapose expectation vs. reality",
  "open_loop": "Explicit question or 'here is what nobody tells you' frame",
  "narration": "Full 30-second narration (50-75 words)"
}

HOOK FORMULAS (pick one):
1. "X costs $Y. But actually it costs $Z." (cost reveal)
2. "Everyone says X. They're wrong." (myth bust)
3. "[Specific person/company] just [shocking action]. Here's why." (news hook)
4. "I analyzed N [things]. Only M got it right." (data authority)
5. "$X,XXX in Y seconds. Here's how." (result first)

RULES:
- First word must grab attention (number, name, or "this")
- Never start with "hey", "so", "did you know", or any greeting
- Include at least one specific number in the first sentence
- The open loop must create genuine curiosity, not clickbait`;

Step 3: Body and Pacing Generation

const bodyGenerationPrompt = `You are a video scriptwriter who creates retention-optimized
scripts for faceless finance content.

HOOK (from previous step):
{{hookJson}}

FACTS (from research step):
{{factsJson}}

FORMAT: {{format}}
TARGET DURATION: {{durationSeconds}} seconds
TARGET WORD COUNT: {{Math.round(durationSeconds * 3)}} words (at 180 WPM)

OUTPUT FORMAT:
{
  "scenes": [
    {
      "timestamp": "0:00-0:03",
      "narration": "The spoken text for this scene",
      "visual_directive": "Description of what should appear on screen",
      "text_overlay": "Any text that appears on screen (null if none)",
      "transition": "cut | fade | zoom | slide"
    }
  ],
  "total_words": number,
  "estimated_duration_seconds": number,
  "stock_footage_keywords": ["keyword1", "keyword2"]
}

PACING RULES:
- Scene changes every 3-5 seconds (short-form) or 8-15 seconds (long-form)
- Visual directive must change with each new fact
- Text overlays for NUMBERS only (show the $X,XXX on screen)
- Transitions: 80% cuts, 15% zooms, 5% fades (never slide for finance)
- Pattern interrupt every 90 seconds in long-form (unexpected visual, tone shift, question)

NARRATION RULES:
- Short sentences (8-15 words average)
- One idea per sentence
- Active voice only ("California charges" not "you are charged by California")
- Conversational but authoritative (no "um", no hedging, no "I think")
- Specific numbers spoken as words ("three thousand two hundred" not "3,200")
  but displayed as numerals on screen

VISUAL DIRECTIVE RULES:
- Dark background default (#0B0C10)
- Numbers appear in accent color (#00FF00 or #FFD700)
- Comparison visuals: side-by-side, never sequential
- No human faces unless the subject of the video
- Stock footage: abstract business, money, documents (never cheesy handshakes)`;

Key insight: The prompt chain matters because each step produces structured output that feeds the next step. Single-prompt scripts are inconsistent. The chain gives you: (1) verified facts with surprise ranking, (2) a hook engineered for retention, (3) a paced script with visual directives that map directly to your render pipeline.


The Finance Aesthetic

Finance content that performs follows a specific visual language. It is not accidental β€” it signals authority and creates urgency.

interface VisualSystem {
  palette: {
    background: '#0B0C10';      // Deep Void β€” dark, cinematic
    primary: '#C5C6C7';         // Light gray text
    accent1: '#00FF00';         // Terminal Green β€” for money/positive numbers
    accent2: '#FFD700';         // Gold β€” for highlights
    warning: '#FF3B30';         // Warning Red β€” for costs/dangers
    surface: '#1F2833';         // Slightly lighter surface
  };
  typography: {
    headline: 'Inter Black';     // Bold, geometric, modern
    data: 'JetBrains Mono';     // Monospace for numbers and data
    body: 'Inter Regular';       // Clean reading
    caption: 'Courier New';      // Terminal aesthetic for overlays
  };
  motion: {
    textEntry: 'slide-up 0.3s ease-out';
    numberCount: 'count-up 0.8s linear';
    sceneTransition: 'cut';      // Hard cuts, not dissolves
    emphasis: 'scale(1.1) 0.2s'; // Subtle pulse on key numbers
  };
}

The Color Rules

Background:   ALWAYS dark (#0B0C10 or #1a1a2e)
              Never white. Never light gray. Dark = authority in finance.

Money UP:     Terminal Green (#00FF00) or Gold (#FFD700)
              Use when showing positive numbers, gains, savings

Money DOWN:   Warning Red (#FF3B30)
              Use when showing costs, losses, traps, fees

Comparisons:  Green vs. Red side-by-side
              The viewer's eye immediately understands the story

Text:         White (#FFFFFF) or light gray (#C5C6C7)
              High contrast on dark background. No colored body text.

Accents:      Use sparingly. One accent color per scene maximum.
              Too many colors = cheap. Restraint = premium.

The 3-Second Rule

Every video starts in media res. No logos, no intros, no β€œwelcome to my channel.” The first frame is already content.

BAD first 3 seconds:
  [Logo animation] β†’ [Channel name] β†’ [Subscribe reminder]
  Result: 40% drop in first 3 seconds

GOOD first 3 seconds:
  [Dark screen] β†’ [Red "$4,000" fills screen] β†’ [Voice: "Filing an LLC costs $50..."]
  Result: 5% drop in first 3 seconds

Pattern Interrupts (Long-Form)

Retention drops steadily in long-form content unless you interrupt the pattern every 60-90 seconds. Each interrupt resets the viewer’s attention.

const patternInterrupts = [
  {
    type: 'visual_shift',
    description: 'Change the background color or scene composition dramatically',
    when: 'Every 60-90 seconds',
    example: 'Switch from dark bg to a stock footage montage for 3 seconds',
  },
  {
    type: 'rhetorical_question',
    description: 'Ask a question the viewer answers internally',
    when: 'Before introducing a new major point',
    example: '"But here is the question nobody asks: why does it cost that much?"',
  },
  {
    type: 'number_reveal',
    description: 'Build up to a surprising number, then reveal it with animation',
    when: 'At the climax of each data point',
    example: 'Count-up animation from $0 to $3,200',
  },
  {
    type: 'contrast_cut',
    description: 'Jump-cut between two opposing visuals',
    when: 'When comparing two options',
    example: 'California flag ($800/yr) β†’ Wyoming flag ($50 once)',
  },
  {
    type: 'silence_beat',
    description: '0.5-1 second of silence after a major claim',
    when: 'After the most surprising fact in each section',
    example: '"...even if you made zero dollars." [0.8s silence] "And it gets worse."',
  },
];

Stock Footage Treatment

Raw stock footage looks generic. The treatment makes it yours.

Filter chain (FFmpeg):
  1. Darken: brightness=-0.15
  2. Contrast boost: contrast=1.2
  3. Color temperature: cool shift (blues)
  4. Vignette: subtle edge darkening
  5. Grain: add film grain overlay at 10% opacity
  6. Speed: 1.2x-1.5x (stock footage always feels slow)

FFmpeg command:
  -vf "eq=brightness=-0.15:contrast=1.2,\
       colorbalance=bs=0.1:ms=0.05,\
       vignette=PI/4,\
       noise=alls=10:allf=t,\
       setpts=PTS/1.3"

Thumbnail Design Principles

Thumbnails are the other half of the click decision. For faceless finance content:

Rules:
  1. Maximum 4 words (6 absolute max)
  2. One dominant color against dark background
  3. Number must be visible at thumbnail size (mobile)
  4. No human faces (unless the subject β€” e.g., celebrity bankruptcy)
  5. High contrast: text must pop at 120x90px (mobile search)
  6. Warning Red for negative topics, Gold for positive topics

Layout:
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚                      β”‚
  β”‚   $4,000             β”‚  ← Large number in accent color
  β”‚   LLC TRAP           β”‚  ← 2-3 word title in white
  β”‚                      β”‚
  β”‚         [icon]       β”‚  ← Simple icon (calculator, warning, etc.)
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Two paths exist. Choose based on your quality needs, budget, and scale.

Path A: Local FFmpeg ($0.01/video)

This is the zero-cost path. It uses free TTS, free stock footage, and local GPU rendering. Quality is lower than cloud rendering but sufficient for TikTok and testing.

Hardware requirements:

The Stack:

Script (JSON) β†’ edge-tts (free TTS) β†’ Pexels API (stock footage)
    β†’ FFmpeg composite β†’ MP4 (1080x1920 for shorts, 1920x1080 for longs)

Complete produce.py β€” Topic to Finished MP4:

#!/usr/bin/env python3
"""
produce.py β€” Generate a complete short-form video from a topic.
Dependencies: pip install edge-tts httpx openai pydub
System: ffmpeg with NVENC support
"""

import asyncio
import json
import subprocess
import tempfile
from pathlib import Path
from dataclasses import dataclass
from typing import Optional

import edge_tts
import httpx


PEXELS_API_KEY = "YOUR_PEXELS_API_KEY"  # Free at pexels.com/api
OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"  # Or use any OpenAI-compatible API
OUTPUT_DIR = Path("./output")
OUTPUT_DIR.mkdir(exist_ok=True)

BG_COLOR = "0x0B0C10"
ACCENT_COLOR = "0x00FF00"
WARNING_COLOR = "0xFF3B30"
TEXT_COLOR = "0xFFFFFF"
FONT_PATH = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
VOICE = "en-US-GuyNeural"  # Male, authoritative
WIDTH, HEIGHT = 1080, 1920  # 9:16 portrait


@dataclass
class VideoScene:
    timestamp: str
    narration: str
    visual_directive: str
    text_overlay: Optional[str]
    stock_keyword: str


@dataclass
class VideoScript:
    title: str
    hook_text: str
    cta_text: str
    scenes: list[VideoScene]
    hashtags: list[str]



async def generate_script(topic: str, trend_context: str) -> VideoScript:
    """Generate a structured video script using an LLM."""
    client = httpx.AsyncClient()

    prompt = f"""You are a viral finance video scriptwriter. Create a 45-60 second
TikTok script about: {topic}

Trend context: {trend_context}

Output ONLY valid JSON with this structure:
{{
  "title": "Video title for posting",
  "hook_text": "3-5 word screen text for first 3 seconds (ALL CAPS)",
  "cta_text": "CTA text for last 3 seconds",
  "scenes": [
    {{
      "timestamp": "0:00-0:05",
      "narration": "Spoken text for this scene",
      "visual_directive": "What appears on screen",
      "text_overlay": "$X,XXX number overlay or null",
      "stock_keyword": "one keyword for stock footage search"
    }}
  ],
  "hashtags": ["hashtag1", "hashtag2"]
}}

RULES:
- 6-8 scenes total, each 5-8 seconds
- Total narration: 130-160 words (45-55 seconds at 180 WPM)
- Hook in scene 1: shocking number or provocative claim
- Every scene has a different stock_keyword
- text_overlay only for numbers (show dollars/percentages)
- CTA: "Follow for more" or "Link in bio" style
- No filler words, no greetings, no "hey guys"
- Use specific numbers, never approximations"""

    response = await client.post(
        "https://api.openai.com/v1/chat/completions",
        headers={"Authorization": f"Bearer {OPENAI_API_KEY}"},
        json={
            "model": "gpt-4o-mini",
            "messages": [{"role": "user", "content": prompt}],
            "response_format": {"type": "json_object"},
            "temperature": 0.7,
        },
        timeout=30.0,
    )

    data = response.json()["choices"][0]["message"]["content"]
    parsed = json.loads(data)

    scenes = [
        VideoScene(
            timestamp=s["timestamp"],
            narration=s["narration"],
            visual_directive=s["visual_directive"],
            text_overlay=s.get("text_overlay"),
            stock_keyword=s["stock_keyword"],
        )
        for s in parsed["scenes"]
    ]

    return VideoScript(
        title=parsed["title"],
        hook_text=parsed["hook_text"],
        cta_text=parsed["cta_text"],
        scenes=scenes,
        hashtags=parsed.get("hashtags", []),
    )



async def generate_tts(text: str, output_path: Path) -> float:
    """Generate TTS audio using Microsoft Edge's free TTS service.
    Returns duration in seconds."""
    communicate = edge_tts.Communicate(text, VOICE, rate="+10%")
    await communicate.save(str(output_path))

    # Get duration via ffprobe
    result = subprocess.run(
        ["ffprobe", "-v", "error", "-show_entries", "format=duration",
         "-of", "default=noprint_wrappers=1:nokey=1", str(output_path)],
        capture_output=True, text=True,
    )
    return float(result.stdout.strip())



async def search_stock_video(keyword: str) -> Optional[str]:
    """Search Pexels for vertical stock video. Returns download URL."""
    async with httpx.AsyncClient() as client:
        response = await client.get(
            "https://api.pexels.com/videos/search",
            params={"query": keyword, "per_page": 3, "orientation": "portrait"},
            headers={"Authorization": PEXELS_API_KEY},
        )

        if response.status_code != 200:
            return None

        videos = response.json().get("videos", [])
        if not videos:
            return None

        # Pick best quality MP4, prefer closest to 1080p height
        video = videos[0]
        files = sorted(
            [f for f in video["video_files"] if f["file_type"] == "video/mp4"],
            key=lambda f: abs(f.get("height", 0) - 1920),
        )
        return files[0]["link"] if files else None


async def download_video(url: str, output_path: Path) -> None:
    """Download a video file to disk."""
    async with httpx.AsyncClient(follow_redirects=True) as client:
        response = await client.get(url)
        output_path.write_bytes(response.content)



def composite_video(
    audio_path: Path,
    clips: list[Path],
    hook_text: str,
    cta_text: str,
    text_overlays: list[Optional[str]],
    output_path: Path,
    duration: float,
) -> None:
    """Composite stock clips with audio, text overlays, and captions."""

    # Calculate clip duration (divide total duration evenly)
    clip_duration = duration / max(len(clips), 1)

    # Build FFmpeg filter complex
    inputs = []
    filter_parts = []

    # Input: audio
    inputs.extend(["-i", str(audio_path)])

    # Input: each stock clip
    for i, clip in enumerate(clips):
        inputs.extend(["-i", str(clip)])

    # Trim and scale each clip, apply visual treatment
    for i in range(len(clips)):
        input_idx = i + 1  # 0 is audio
        start = i * clip_duration
        filter_parts.append(
            f"[{input_idx}:v]"
            f"trim=0:{clip_duration},setpts=PTS-STARTPTS,"
            f"scale={WIDTH}:{HEIGHT}:force_original_aspect_ratio=increase,"
            f"crop={WIDTH}:{HEIGHT},"
            f"eq=brightness=-0.15:contrast=1.2,"
            f"setpts=PTS/1.2"
            f"[clip{i}]"
        )

    # Concatenate clips
    concat_inputs = "".join(f"[clip{i}]" for i in range(len(clips)))
    filter_parts.append(
        f"{concat_inputs}concat=n={len(clips)}:v=1:a=0[bgvideo]"
    )

    # Add hook text overlay (first 3 seconds)
    filter_parts.append(
        f"[bgvideo]drawtext="
        f"fontfile={FONT_PATH}:"
        f"text='{hook_text}':"
        f"fontcolor=white:"
        f"fontsize=72:"
        f"x=(w-text_w)/2:y=h*0.15:"
        f"enable='between(t,0,3)'"
        f"[withhook]"
    )

    # Add CTA text overlay (last 4 seconds)
    cta_start = max(0, duration - 4)
    filter_parts.append(
        f"[withhook]drawtext="
        f"fontfile={FONT_PATH}:"
        f"text='{cta_text}':"
        f"fontcolor=white:"
        f"fontsize=56:"
        f"box=1:boxcolor={WARNING_COLOR}@0.8:boxborderw=15:"
        f"x=(w-text_w)/2:y=h*0.40:"
        f"enable='between(t,{cta_start},{duration})'"
        f"[final]"
    )

    filter_complex = ";".join(filter_parts)

    cmd = [
        "ffmpeg", "-y",
        *inputs,
        "-filter_complex", filter_complex,
        "-map", "[final]",
        "-map", "0:a",
        "-c:v", "h264_nvenc",  # NVIDIA GPU encoding
        "-preset", "p4",
        "-b:v", "8M",
        "-c:a", "aac",
        "-b:a", "192k",
        "-shortest",
        "-movflags", "+faststart",
        str(output_path),
    ]

    subprocess.run(cmd, check=True, capture_output=True)



async def produce(topic: str, trend_context: str = "") -> Path:
    """Full pipeline: topic -> finished MP4."""

    print(f"[1/4] Generating script for: {topic}")
    script = await generate_script(topic, trend_context)
    print(f"       Title: {script.title}")
    print(f"       Scenes: {len(script.scenes)}")

    # Generate TTS for full narration
    print("[2/4] Generating TTS audio...")
    full_narration = " ".join(scene.narration for scene in script.scenes)
    audio_path = OUTPUT_DIR / "narration.mp3"
    duration = await generate_tts(full_narration, audio_path)
    print(f"       Duration: {duration:.1f}s")

    # Search and download stock footage for each scene
    print("[3/4] Downloading stock footage...")
    clip_paths: list[Path] = []
    text_overlays: list[Optional[str]] = []

    for i, scene in enumerate(script.scenes):
        url = await search_stock_video(scene.stock_keyword)
        if url:
            clip_path = OUTPUT_DIR / f"clip_{i}.mp4"
            await download_video(url, clip_path)
            clip_paths.append(clip_path)
            text_overlays.append(scene.text_overlay)
            print(f"       Scene {i}: '{scene.stock_keyword}' -> downloaded")
        else:
            print(f"       Scene {i}: '{scene.stock_keyword}' -> no footage found")

    if not clip_paths:
        raise RuntimeError("No stock footage found for any scene")

    # Composite final video
    print("[4/4] Compositing final video...")
    output_path = OUTPUT_DIR / f"{topic.lower().replace(' ', '-')}.mp4"

    composite_video(
        audio_path=audio_path,
        clips=clip_paths,
        hook_text=script.hook_text,
        cta_text=script.cta_text,
        text_overlays=text_overlays,
        output_path=output_path,
        duration=duration,
    )

    print(f"\nDone! Output: {output_path}")
    print(f"Title: {script.title}")
    print(f"Hashtags: {' '.join('#' + h for h in script.hashtags)}")

    # Save script metadata
    meta_path = output_path.with_suffix(".json")
    meta_path.write_text(json.dumps({
        "title": script.title,
        "hook_text": script.hook_text,
        "cta_text": script.cta_text,
        "hashtags": script.hashtags,
        "duration": duration,
        "scenes": [
            {
                "timestamp": s.timestamp,
                "narration": s.narration,
                "text_overlay": s.text_overlay,
                "stock_keyword": s.stock_keyword,
            }
            for s in script.scenes
        ],
    }, indent=2))

    return output_path


if __name__ == "__main__":
    import sys
    topic = sys.argv[1] if len(sys.argv) > 1 else "LLC cost breakdown"
    trend = sys.argv[2] if len(sys.argv) > 2 else "small business formation trending"
    asyncio.run(produce(topic, trend))

Path B: Cloud API (Creatomate β€” $0.30-$6/video)

The cloud path trades cost for quality. Creatomate handles TTS (via ElevenLabs integration), animated captions, text overlays, transitions, and final rendering β€” all via a single API call.

The Stack:

Script (JSON) β†’ buildRenderScript() β†’ Creatomate POST /renders
    β†’ webhook notification β†’ download MP4 from Creatomate CDN

Complete video-renderer.ts β€” Production-Grade Cloud Rendering:

/**
 * video-renderer.ts β€” Dual backend: local (FFmpeg) or Creatomate cloud.
 *
 * Local flow:  script JSON β†’ POST /render/async β†’ poll /status/<id> β†’ MP4
 * Cloud flow:  script JSON β†’ RenderScript β†’ Creatomate POST /renders β†’ webhook β†’ MP4
 */

// --- Types ---

export interface VideoScript {
  /** Brand slug for attribution */
  brand_slug: string;
  /** Social account handle */
  handle: string;
  /** Unique draft ID */
  draft_id: string;
  /** The narration text (read aloud via TTS) */
  narration: string;
  /** Hook text overlay (first 3 seconds) */
  hook_text: string;
  /** CTA text overlay (last 3 seconds) */
  cta_text: string;
  /** Background video URL (Pexels or similar) */
  background_url?: string;
  /** Brand color hex */
  brand_color?: string;
  /** Scene-level breakdown for complex videos */
  scenes?: SceneConfig[];
}

export interface SceneConfig {
  narration: string;
  text_overlay?: string;
  background_url?: string;
  duration?: number;
}

export interface RenderResponse {
  id: string;
  status: 'planned' | 'rendering' | 'succeeded' | 'failed';
  url?: string;
  error_message?: string;
}

// --- RenderScript Builder ---

/**
 * Build a Creatomate RenderScript for a short-form vertical video.
 * Output: 1080x1920 (9:16), up to 90 seconds, voiceover + captions + background.
 */
export function buildRenderScript(
  script: VideoScript,
  webhookUrl: string
): Record<string, unknown> {
  const brandColor = script.brand_color || '#1a1a2e';
  const accentColor = '#e94560';

  const elements: Record<string, unknown>[] = [
    // Background β€” video if provided, solid color fallback
    ...(script.background_url
      ? [
          {
            type: 'video',
            track: 1,
            source: script.background_url,
            fit: 'cover',
            color_filter: [{ type: 'brightness', value: '60%' }],
          },
        ]
      : [
          {
            type: 'shape',
            track: 1,
            fill_color: brandColor,
          },
        ]),

    // Voiceover via ElevenLabs (built into Creatomate)
    {
      name: 'Voiceover',
      type: 'audio',
      track: 2,
      provider:
        'elevenlabs model_id=eleven_multilingual_v2 voice_id=pNInz6obpgDQGcFmaJgB stability=0.5 similarity_boost=0.75',
      source: script.narration,
    },

    // Auto-synced captions from voiceover
    {
      type: 'text',
      track: 3,
      y: '75%',
      width: '90%',
      height: '20%',
      x_alignment: '50%',
      y_alignment: '50%',
      fill_color: '#ffffff',
      stroke_color: '#000000',
      stroke_width: '0.8 vmin',
      font_family: 'Montserrat',
      font_weight: '800',
      font_size: '6.5 vmin',
      line_height: '150%',
      text_transform: 'uppercase',
      transcript_source: 'Voiceover',
      transcript_effect: 'highlight',
      transcript_color: accentColor,
    },

    // Hook text overlay (first 3 seconds)
    {
      type: 'text',
      track: 4,
      time: 0,
      duration: 3,
      y: '15%',
      width: '85%',
      height: '15%',
      x_alignment: '50%',
      y_alignment: '50%',
      fill_color: '#ffffff',
      font_family: 'Montserrat',
      font_weight: '900',
      font_size: '8 vmin',
      text_transform: 'uppercase',
      text: script.hook_text,
      animations: [
        {
          time: 0,
          duration: 0.5,
          easing: 'quadratic-out',
          type: 'text-slide',
          scope: 'split-clip',
          split: 'word',
        },
        {
          time: 'end',
          duration: 0.3,
          type: 'fade',
        },
      ],
    },

    // CTA text overlay (last 4 seconds)
    {
      type: 'text',
      track: 5,
      time: 'end',
      duration: 4,
      y: '40%',
      width: '80%',
      height: '10%',
      x_alignment: '50%',
      y_alignment: '50%',
      fill_color: accentColor,
      font_family: 'Montserrat',
      font_weight: '700',
      font_size: '5.5 vmin',
      background_color: '#ffffff',
      background_x_padding: '50%',
      background_y_padding: '30%',
      background_border_radius: '30%',
      text: script.cta_text,
      animations: [
        {
          time: 0,
          duration: 0.5,
          easing: 'quadratic-out',
          type: 'slide',
          direction: 'up',
        },
      ],
    },
  ];

  return {
    output_format: 'mp4',
    width: 1080,
    height: 1920,
    max_duration: 90,
    elements,
    webhook_url: webhookUrl,
    metadata: JSON.stringify({
      draft_id: script.draft_id,
      handle: script.handle,
      brand_slug: script.brand_slug,
    }),
  };
}

// --- Creatomate API Client ---

const CREATOMATE_API_KEY = process.env.CREATOMATE_API_KEY!;
const CREATOMATE_BASE = 'https://api.creatomate.com/v2';

export async function submitCloudRender(
  script: VideoScript,
  webhookUrl: string
): Promise<RenderResponse> {
  const renderScript = buildRenderScript(script, webhookUrl);

  const res = await fetch(`${CREATOMATE_BASE}/renders`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${CREATOMATE_API_KEY}`,
    },
    body: JSON.stringify(renderScript),
  });

  if (!res.ok) {
    const errText = await res.text();
    throw new Error(`Creatomate render failed (${res.status}): ${errText}`);
  }

  const data = (await res.json()) as RenderResponse[] | RenderResponse;
  return Array.isArray(data) ? data[0] : data;
}

// --- Local Render Backend ---

export async function submitLocalRender(
  script: VideoScript,
  baseUrl: string
): Promise<RenderResponse> {
  // Start async render
  const startRes = await fetch(`${baseUrl}/render/async`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      narration: script.narration,
      hook_text: script.hook_text,
      cta_text: script.cta_text,
      bg_color: script.brand_color?.replace('#', '0x') || '0x1a1a2e',
      background_url: script.background_url,
    }),
  });

  if (!startRes.ok) {
    const errText = await startRes.text();
    throw new Error(`Local render failed (${startRes.status}): ${errText}`);
  }

  const { job_id } = (await startRes.json()) as { job_id: string };

  // Poll for completion (max 120s)
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 2000));

    const statusRes = await fetch(`${baseUrl}/status/${job_id}`);

    if (statusRes.status === 200) {
      return { id: job_id, status: 'succeeded', url: `${baseUrl}/output/${job_id}.mp4` };
    }
    if (statusRes.status === 500) {
      const err = (await statusRes.json()) as { error: string };
      throw new Error(`Local render failed: ${err.error}`);
    }
    // 202 = still rendering, continue polling
  }

  throw new Error(`Local render timed out (job_id=${job_id})`);
}

// --- Auto-Select Backend ---

export async function submitRender(
  script: VideoScript,
  webhookUrl: string
): Promise<RenderResponse> {
  const localUrl = process.env.VIDEO_RENDER_URL;
  if (localUrl) {
    return submitLocalRender(script, localUrl);
  }
  return submitCloudRender(script, webhookUrl);
}

Both paths need stock footage. Here is a complete search function that works with Pexels and falls back to Pixabay.

interface StockVideo {
  id: string;
  url: string;
  width: number;
  height: number;
  duration: number;
  source: 'pexels' | 'pixabay';
}

async function searchStockVideo(
  keyword: string,
  orientation: 'portrait' | 'landscape' = 'portrait'
): Promise<StockVideo | null> {
  // Try Pexels first
  const pexelsResult = await searchPexels(keyword, orientation);
  if (pexelsResult) return pexelsResult;

  // Fallback to Pixabay
  return searchPixabay(keyword, orientation);
}

async function searchPexels(
  keyword: string,
  orientation: string
): Promise<StockVideo | null> {
  const res = await fetch(
    `https://api.pexels.com/videos/search?query=${encodeURIComponent(keyword)}&per_page=5&orientation=${orientation}`,
    { headers: { Authorization: process.env.PEXELS_API_KEY! } }
  );

  if (!res.ok) return null;

  const data = await res.json() as {
    videos: Array<{
      id: number;
      duration: number;
      video_files: Array<{
        quality: string;
        file_type: string;
        width: number;
        height: number;
        link: string;
      }>;
    }>;
  };

  if (!data.videos?.length) return null;

  const video = data.videos[0];
  const file = video.video_files
    .filter((f) => f.file_type === 'video/mp4')
    .sort((a, b) => Math.abs(a.height - 1920) - Math.abs(b.height - 1920))[0];

  if (!file) return null;

  return {
    id: String(video.id),
    url: file.link,
    width: file.width,
    height: file.height,
    duration: video.duration,
    source: 'pexels',
  };
}

async function searchPixabay(
  keyword: string,
  orientation: string
): Promise<StockVideo | null> {
  const pixabayOrientation = orientation === 'portrait' ? 'vertical' : 'horizontal';

  const res = await fetch(
    `https://pixabay.com/api/videos/?key=${process.env.PIXABAY_API_KEY}&q=${encodeURIComponent(keyword)}&per_page=5&video_type=all`
  );

  if (!res.ok) return null;

  const data = await res.json() as {
    hits: Array<{
      id: number;
      duration: number;
      videos: {
        large: { url: string; width: number; height: number };
        medium: { url: string; width: number; height: number };
      };
    }>;
  };

  if (!data.hits?.length) return null;

  const hit = data.hits[0];
  const file = hit.videos.large || hit.videos.medium;

  return {
    id: String(hit.id),
    url: file.url,
    width: file.width,
    height: file.height,
    duration: hit.duration,
    source: 'pixabay',
  };
}

TTS Options Compared

interface TTSOption {
  name: string;
  cost: string;
  quality: 'low' | 'medium' | 'high' | 'premium';
  latency: string;
  voices: number;
  integration: string;
}

const ttsOptions: TTSOption[] = [
  {
    name: 'edge-tts (Python)',
    cost: 'Free',
    quality: 'medium',
    latency: '1-3s per paragraph',
    voices: 300,
    integration: 'pip install edge-tts',
    // Uses Microsoft Edge's online TTS service
    // No API key needed
    // Good enough for TikTok, not for premium YouTube
  },
  {
    name: 'edge-tts-universal (Node.js)',
    cost: 'Free',
    quality: 'medium',
    latency: '1-3s per paragraph',
    voices: 300,
    integration: 'npm install edge-tts-universal',
    // TypeScript port of the Python package
    // Works in Node.js, Deno, Bun
  },
  {
    name: 'ElevenLabs',
    cost: '$0.30/1K chars ($5-$99/mo plans)',
    quality: 'premium',
    latency: '2-5s per paragraph',
    voices: 1000,
    integration: 'REST API or Creatomate built-in',
    // Best quality on market
    // Voice cloning available
    // Built into Creatomate β€” no separate API call needed
  },
  {
    name: 'Google Cloud TTS',
    cost: '$4/1M chars (Standard), $16/1M chars (WaveNet)',
    quality: 'high',
    latency: '1-2s per paragraph',
    voices: 400,
    integration: 'REST API + client libraries',
  },
  {
    name: 'Amazon Polly',
    cost: '$4/1M chars (Standard), $16/1M chars (Neural)',
    quality: 'high',
    latency: '1-2s per paragraph',
    voices: 60,
    integration: 'AWS SDK',
  },
];

Key insight: For testing and TikTok, use edge-tts (free). For YouTube long-form where voice quality directly affects retention, use ElevenLabs ($5/mo starter plan gives 30,000 characters β€” enough for ~10 long-form videos). The quality difference is significant: edge-tts has audible robotic artifacts that sophisticated viewers notice.

FFmpeg Composite Commands Reference

These are the building-block FFmpeg commands for local rendering.

Overlay text on video:

ffmpeg -i background.mp4 \
  -vf "drawtext=fontfile=/path/to/Inter-Black.ttf:\
       text='THE LLC TRAP':\
       fontcolor=white:\
       fontsize=72:\
       x=(w-text_w)/2:y=h*0.15:\
       enable='between(t,0,3)'" \
  -c:a copy output.mp4

Merge TTS audio with video:

ffmpeg -i video.mp4 -i narration.mp3 \
  -c:v copy -c:a aac \
  -map 0:v:0 -map 1:a:0 \
  -shortest \
  output.mp4

Concatenate clips with transitions:

echo "file 'clip1.mp4'" > clips.txt
echo "file 'clip2.mp4'" >> clips.txt
echo "file 'clip3.mp4'" >> clips.txt

ffmpeg -f concat -safe 0 -i clips.txt \
  -c:v h264_nvenc -preset p4 -b:v 8M \
  -c:a aac -b:a 192k \
  concatenated.mp4

Apply the finance aesthetic filter chain:

ffmpeg -i raw_stock.mp4 \
  -vf "eq=brightness=-0.15:contrast=1.2,\
       colorbalance=bs=0.1:ms=0.05,\
       vignette=PI/4,\
       setpts=PTS/1.3" \
  -c:v h264_nvenc -preset p4 \
  treated.mp4

Scale and crop to 9:16 portrait:

ffmpeg -i landscape.mp4 \
  -vf "scale=1080:1920:force_original_aspect_ratio=increase,\
       crop=1080:1920" \
  portrait.mp4

Add animated number counter overlay (using drawtext with timecodes):

ffmpeg -i background.mp4 \
  -vf "drawtext=fontfile=/path/to/JetBrainsMono-Bold.ttf:\
       text='$%{eif\\:min(3200\\,3200*t/2)\\:d}':\
       fontcolor=#00FF00:\
       fontsize=96:\
       x=(w-text_w)/2:y=(h-text_h)/2:\
       enable='between(t,2,4)'" \
  -c:a copy output.mp4

The most effective video strategy is not choosing between short-form and long-form. It is using short-form as a funnel to long-form.

The Architecture

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  LONG-FORM      β”‚  ← Cash engine
                    β”‚  YouTube 8-15m  β”‚     $9-$25 RPM
                    β”‚  2-3x per week  β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
                    Extract 3 best segments
                             β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚              β”‚              β”‚
         β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”
         β”‚ SHORT 1 β”‚   β”‚ SHORT 2 β”‚   β”‚ SHORT 3 β”‚  ← Amplification
         β”‚ TikTok  β”‚   β”‚ YT Shortβ”‚   β”‚ IG Reel β”‚     $0.40-$2.00 RPM
         β”‚ 60s     β”‚   β”‚ 60s     β”‚   β”‚ 60s     β”‚     But drives subs
         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     to long-form

Why This Works

  1. Long-form is the cash engine. Finance YouTube RPM is $9-$25. A single 10-minute video with 50K views = $450-$1,250. Nothing else comes close.

  2. Shorts are top-of-funnel. They do not earn much directly but drive subscriptions. A viewer who discovers you via a Short and subscribes will watch your long-form content for years.

  3. Shorts validate topics. Before investing $2-$6 in a long-form render, post a Short on the same topic. If it gets 10K+ views, the topic has demand. If it gets 200 views, skip the long-form.

  4. Same assets, zero marginal cost. The stock footage, research, and script are already done for the long-form video. Extracting 3 shorts is a prompt + a render, not a production.

Extracting Shorts from Longs

interface ShortExtraction {
  segment: string;
  startTime: number;
  endTime: number;
  hookLine: string;
  viralitySignal: string;
}

const extractionPrompt = `You are analyzing a long-form video script to find the 3 best
60-second segments that would perform as standalone TikTok/Shorts.

FULL SCRIPT:
{{longFormScript}}

For each segment, identify:
1. The most SHOCKING or COUNTERINTUITIVE claim (this becomes the hook)
2. A self-contained narrative arc (problem β†’ insight β†’ takeaway)
3. A natural ending point that creates urgency to learn more

Output JSON array of 3 segments:
[
  {
    "segment": "The exact narration text for this 60-second clip",
    "start_time": 45,
    "end_time": 105,
    "hook_line": "The first sentence, rewritten as a scroll-stopping hook",
    "virality_signal": "Why this segment would perform (e.g., 'counterintuitive claim about California LLC cost')"
  }
]

RULES:
- Each segment must work WITHOUT context from the rest of the video
- The hook must be the FIRST sentence (no buildup)
- Prefer segments with specific dollar amounts
- Avoid segments that reference "earlier in this video"
- Each segment should cover a DIFFERENT subtopic`;

Auto-Rendering 9:16 Crops

If your long-form video is 16:9, you can auto-crop to 9:16 for Shorts without re-rendering the entire video.

ffmpeg -i longform.mp4 \
  -ss 00:00:45 -t 60 \
  -vf "crop=ih*9/16:ih,scale=1080:1920" \
  -c:v h264_nvenc -preset p4 \
  -c:a aac \
  short_segment_1.mp4

This replaces tools like OpusClip ($15-$29/month) for basic extraction. OpusClip adds AI-powered selection of the best moments and auto-captions, which is valuable at scale but unnecessary when you already have the script and know which segments to extract.

The Posting Cadence

Monday:    1 Short (TikTok + YT Shorts + IG Reels)
Tuesday:   1 Long-form (YouTube) + 1 Short (extracted)
Wednesday: 1 Short
Thursday:  1 Long-form (YouTube) + 1 Short (extracted)
Friday:    1 Short
Saturday:  1 Long-form (YouTube) + 1 Short (extracted)
Sunday:    1 Short (recap/compilation)

Weekly total: 3 long-form + 7 shorts = 10 videos
Monthly total: 12 long-form + 28 shorts = 40 videos

Production time (automated pipeline):
  12 long-form x 15 min = 3 hours
  28 shorts x 5 min = 2.3 hours
  Review + approval: 2-3 hours
  Total: ~8 hours/month for 40 videos

TikTok

interface TikTokOptimization {
  hook: {
    timing: '0.5 seconds to grab attention';
    rule: 'First frame must be visually arresting β€” text, color, or motion';
    antiPattern: 'Fade-in from black (instant scroll)';
  };
  sound: {
    strategy: 'Use trending sounds when possible β€” 2.3x algorithm boost';
    rule: 'If using original audio (TTS), add trending sound at 5% volume underneath';
    discovery: 'Check TikTok Creative Center for trending sounds weekly';
  };
  hashtags: {
    count: '5-8 hashtags';
    mix: '2 broad (#finance #money) + 3 niche (#llctax #sidehustle) + 2 trending';
    rule: 'Never use #fyp or #foryou β€” they do nothing and signal amateur';
  };
  format: {
    length: '60-90 seconds for Creator Rewards eligibility (minimum 1 minute)';
    ratio: '9:16 always, 1080x1920';
    captions: 'Required β€” 80% of TikTok is watched without sound';
    textPlacement: 'Keep text in safe zone (top 15% and bottom 20% are covered by UI)';
  };
  contentRewards: {
    eligibility: '10K+ followers, 100K+ views in last 30 days, 1+ minute videos';
    rpm: '$0.40-$2.00 per 1K qualified views';
    qualifiedViews: 'Unique FYP views, 5+ seconds watched, not promoted/paid';
    tip: 'Videos that get shared (not just viewed) earn higher RPM';
  };
}

YouTube Shorts

interface YouTubeShortsOptimization {
  seo: {
    title: 'Include primary keyword β€” YouTube Shorts ARE searchable';
    description: 'Full description with keywords, links, timestamps';
    rule: 'Shorts appear in regular search results β€” treat SEO seriously';
  };
  format: {
    length: '30-60 seconds optimal (max 3 minutes)';
    ratio: '9:16, 1080x1920';
    looping: 'Design for seamless loop β€” last frame should connect to first';
  };
  monetization: {
    rpm: '$0.05-$0.15 (much lower than long-form)';
    purpose: 'Not for direct revenue β€” for subscriber acquisition';
    funnel: 'End card pointing to full video in description';
  };
  algorithm: {
    retentionTarget: '70%+ average view duration for Shorts';
    swipeThroughRate: 'Lower is better β€” means people watch instead of swipe';
    tip: 'Shorts that drive viewers to your long-form get algorithmic boost';
  };
}

YouTube Long-Form

interface YouTubeLongFormOptimization {
  structure: {
    chapters: 'Add chapters every 2-3 minutes β€” required for modern YouTube';
    intro: 'Skip it. Start with the content. Intros kill retention.';
    midRollAds: 'Place manually at natural breaks (every 3-4 minutes)';
    endScreen: 'Last 20 seconds: subscribe CTA + next video recommendation';
  };
  seo: {
    title: 'Primary keyword in first 60 chars, number if possible';
    description: 'First 150 chars = keyword-rich summary (shows in search)';
    tags: '10-15 tags, mix of broad and specific';
    thumbnail: 'Custom always. A/B test with YouTube Studio experiments.';
  };
  retention: {
    target: '40-50% average view duration (industry benchmark for 10-min)';
    hooks: 'Preview the payoff in first 30 seconds';
    patternInterrupt: 'Every 60-90 seconds: visual change, question, or tone shift';
    pacing: '180 WPM narration, 8-15 second scenes, no dead air';
  };
  monetization: {
    threshold: '1,000 subscribers + 4,000 watch hours (or 10M Shorts views)';
    financeRpm: '$9-$25 RPM (US audience)';
    q4Boost: 'October-December RPM is 1.5-2x higher than Q1';
    tip: 'Post more in Q4, less in Q1 β€” same effort, more revenue';
  };
}

Instagram Reels

interface InstagramReelsOptimization {
  format: {
    length: '30-60 seconds (Reels up to 90 seconds, but shorter performs better)';
    ratio: '9:16, 1080x1920';
    coverImage: 'Custom cover that looks good in the grid (squared crop)';
  };
  engagement: {
    cta: 'Ask a question in the caption β€” comments boost distribution';
    carousel: 'Carousel posts with video = 2x engagement vs. standalone Reels';
    textOverlays: 'Essential β€” Instagram is sound-off by default';
  };
  crossPosting: {
    rule: 'Remove TikTok watermark before posting (Instagram penalizes)';
    timing: 'Post to TikTok first, Instagram 24-48 hours later';
    tool: 'SnapTik or similar for watermark removal, or render separate copy';
  };
  monetization: {
    rpm: '$0.10-$0.50 (inconsistent, not primary revenue source)';
    purpose: 'Brand building + traffic driver to YouTube/tools';
  };
}

Cross-Posting Strategy

One video β†’ All platforms simultaneously (with platform-specific tweaks):

1. RENDER: Produce one 9:16 MP4 at 1080x1920
2. TIKTOK: Post with trending sound layered underneath, TikTok-specific hashtags
3. YOUTUBE SHORTS: Post with SEO title, full description, link to long-form
4. INSTAGRAM REELS: Post with engagement CTA in caption, custom cover
5. CONTENT REWARDS: If matching a campaign, post there too for extra RPM

Timing:
  - TikTok: Post first (fastest feedback loop, 6-24 hours to see results)
  - YouTube Shorts: 2-4 hours later
  - Instagram Reels: 24-48 hours later (avoid duplicate content signals)

Never:
  - Post the same video at the exact same time everywhere
  - Use TikTok watermark on other platforms
  - Use the same caption/description across platforms

Not all niches are worth automating. A niche must pass three filters to be viable for programmatic video production.

The Three Filters

interface NicheFilter {
  name: string;
  threshold: string;
  why: string;
}

const filters: NicheFilter[] = [
  {
    name: 'RPM Ceiling',
    threshold: '>$8 RPM on YouTube',
    why: 'Below $8 RPM, you need millions of views to hit $10K/mo. Above $8, you need 500K-1M views β€” achievable in 6-12 months.',
  },
  {
    name: 'Data Extraction Feasibility',
    threshold: 'Historical/analytical topics, not breaking news',
    why: 'Programmatic videos rely on LLM-generated scripts. LLMs are good at analyzing data, terrible at breaking news. Pick niches where the content is analytical, not time-sensitive.',
  },
  {
    name: 'Stock Footage Feasibility',
    threshold: 'Abstract concepts, not specific clips',
    why: 'You need Pexels/Pixabay footage to match your script. Finance = generic office/money footage (abundant). Sports highlights = specific game clips (impossible to source).',
  },
];

Niche Scorecard

NicheRPM RangeData FeasibilityStock FeasibilityOverall Score
Personal Finance$15-$25High (tax data, rates, calculators)High (money, offices, documents)A+
Software/SaaS Reviews$12-$20High (features, pricing, comparisons)Medium (screen recordings)A
Business Cases$10-$18High (company data, revenue, strategy)High (offices, cities, products)A
AI/Tech Explainers$8-$15High (benchmarks, features, workflows)Medium (abstract tech, code)A-
Real Estate$10-$20High (market data, mortgage rates)High (houses, neighborhoods)A
Credit Cards/Banking$20-$45High (rewards, rates, comparisons)Low (specific card imagery)B+
Health/Fitness$4-$8Medium (studies, nutrition data)High (gym, food, exercises)B
Gaming$2-$5Low (gameplay is king, not analysis)Low (need specific game footage)C
Entertainment/Comedy$2-$4Low (personality-driven)Low (need specific clips)D
Music$1-$3Low (subjective, copyright issues)Low (need specific performances)D

Sub-Niche RPM Deep Dive (Finance)

Sub-NicheYouTube RPMTikTok RPMSearch VolumeCompetition
Credit card comparisons$20-$45$1.00-$2.00HighVery High
Tax planning/LLC$15-$25$0.80-$1.50High (seasonal)Medium
Investment strategy$12-$20$0.60-$1.20Very HighHigh
Business formation$10-$18$0.50-$1.00MediumMedium
Debt management$10-$15$0.40-$0.80HighMedium
Budgeting basics$8-$12$0.30-$0.60Very HighVery High
Side hustle guides$8-$15$0.40-$1.00Very HighVery High
Crypto/DeFi$8-$15$0.30-$0.80HighHigh

Key insight: Credit card comparison content has the highest RPM of any niche on YouTube ($20-$45 in Q4), but it requires specific card imagery and detailed comparison data that is hard to generate programmatically. LLC/tax content is the sweet spot: high RPM ($15-$25), easy to generate with LLMs (tax code is public data), and stock footage is abundant (documents, offices, money). Start there.


Once your pipeline works for one video, the next step is scaling it to produce 5-10+ videos per day without proportional time investment. This is where AI agents come in.

The Agent Architecture

interface VideoAgent {
  role: string;
  trigger: string;
  input: string;
  output: string;
  automation: 'full' | 'semi' | 'human-in-loop';
}

const agentPipeline: VideoAgent[] = [
  {
    role: 'Trend Scout',
    trigger: 'Cron: every 6 hours',
    input: 'Virlo API + YouTube Trending + X/Twitter trending',
    output: 'Ranked list of trending topics with niche angles',
    automation: 'full',
  },
  {
    role: 'Script Writer',
    trigger: 'New trending topic identified',
    input: 'Topic + trend context + niche connection',
    output: 'Structured VideoScript JSON',
    automation: 'full',
  },
  {
    role: 'Asset Gatherer',
    trigger: 'Script approved',
    input: 'Stock footage keywords from script',
    output: 'Downloaded clips + TTS audio',
    automation: 'full',
  },
  {
    role: 'Renderer',
    trigger: 'Assets ready',
    input: 'VideoScript + clips + audio',
    output: 'Finished MP4',
    automation: 'full',
  },
  {
    role: 'Quality Gate',
    trigger: 'Render complete',
    input: 'Finished MP4 + script metadata',
    output: 'Approved or rejected with feedback',
    automation: 'human-in-loop',
    // CRITICAL: Human reviews every video before posting
    // AI generates, human approves
  },
  {
    role: 'Publisher',
    trigger: 'Human approval',
    input: 'Approved MP4 + platform metadata',
    output: 'Posted to TikTok, YouTube, Instagram',
    automation: 'semi',
    // Human clicks "approve" β†’ agent posts to all platforms
  },
];

Issue-Per-Video System

Each video is tracked as a GitHub issue. This gives you version history, comments for feedback, labels for categorization, and automation hooks.

interface VideoIssue {
  title: string;          // "VF042: The 401K Calculator Hack"
  labels: string[];       // ["video", "short-form", "llc-tax", "automated"]
  body: {
    topic: string;
    trend_context: string;
    script: string;       // JSON script
    render_status: 'pending' | 'rendering' | 'complete' | 'failed';
    render_url?: string;  // Link to MP4 in R2/CDN
    posted_platforms?: string[];
    views_48h?: number;   // Performance tracking
  };
}

// Example GitHub Issue body:
const exampleIssue = `

**Topic:** 401K contribution calculator reveals most people under-contribute by $4,200/year
**Trend:** "Retirement savings" trending on TikTok finance after viral post about $1M by 65
**Format:** Short-form (60 seconds)
**Brand:** llc-tax

### Script
\`\`\`json
{
  "hook_text": "YOU'RE LOSING $4,200/YEAR",
  "narration": "Most people think they're maxing out their 401K...",
  "cta_text": "FREE CALCULATOR IN BIO"
}
\`\`\`

### Render Status
- [ ] Script generated
- [ ] TTS rendered
- [ ] Stock footage downloaded
- [ ] Video composited
- [ ] Human review passed
- [ ] Posted to TikTok
- [ ] Posted to YouTube Shorts
- [ ] Posted to Instagram Reels

### Performance (48h)
- TikTok views: pending
- YouTube views: pending
`;

Cron-Based Production

// video-factory.ts β€” Cron job that produces N videos per day

interface ProductionConfig {
  videosPerDay: number;
  formats: ('short-form' | 'long-form')[];
  niches: string[];
  renderBackend: 'local' | 'cloud';
  requireHumanReview: true; // Always true. Never auto-post.
}

const config: ProductionConfig = {
  videosPerDay: 5,
  formats: ['short-form'],
  niches: ['personal-finance', 'llc-tax', 'side-hustle'],
  renderBackend: 'local',
  requireHumanReview: true,
};

// Cron schedule:
// 06:00 β€” Trend Scout runs, identifies top 10 topics
// 07:00 β€” Script Writer generates 5 scripts
// 08:00 β€” Asset Gatherer downloads footage + TTS
// 09:00 β€” Renderer produces 5 MP4s
// 10:00 β€” Human reviews queue (GitHub issues labeled "needs-review")
// 11:00 β€” Publisher posts approved videos

// Implementation: each step is a separate function triggered by cron
// or by the completion of the previous step (event-driven)

Quality Rules for Automated Content

These are non-negotiable rules that every generated video must pass before posting.

const qualityRules = [
  {
    rule: 'No fabricated data',
    check: 'Every number in the script must trace to a real source',
    consequence: 'One fake stat destroys channel credibility permanently',
  },
  {
    rule: 'Financial disclaimers',
    check: 'Video description includes: "This is not financial advice"',
    consequence: 'FTC violation, potential legal liability',
  },
  {
    rule: 'No expert claims',
    check: 'Script never says "I am a CPA/attorney/financial advisor"',
    consequence: 'Misrepresentation of credentials, legal liability',
  },
  {
    rule: 'YouTube AI disclosure',
    check: 'Video uses YouTube Studio "altered content" disclosure',
    consequence: 'Policy violation, potential demonetization',
  },
  {
    rule: 'Duration check',
    check: 'Short-form: 60-90 seconds. Long-form: 8-15 minutes.',
    consequence: 'Under 60s = no TikTok Creator Rewards. Over 15m = retention drops.',
  },
  {
    rule: 'Hook verification',
    check: 'First 3 seconds contain visual hook + specific number',
    consequence: 'No hook = no retention = no views',
  },
  {
    rule: 'Stock footage relevance',
    check: 'Each clip matches the narration topic (not generic filler)',
    consequence: 'Irrelevant footage signals low quality to algorithm',
  },
  {
    rule: 'Audio quality',
    check: 'No clipping, no dead air > 1.5s, consistent volume',
    consequence: 'Bad audio = immediate scroll',
  },
];

Key insight: The human review gate is not optional. AI-generated content without human oversight leads to embarrassing mistakes, compliance violations, and channel termination. The agent system generates 5-10 videos per day; the human reviews and approves 3-7 of them. This is 10-15 minutes of review time per day, not hours of production time. That is the efficiency gain.


Layer 1: YouTube AdSense (Long-Form)

The primary revenue driver. Finance long-form YouTube content earns the highest RPM of any niche.

Requirements:
  - YouTube Partner Program: 1,000 subscribers + 4,000 watch hours
  - Or: 1,000 subscribers + 10 million Shorts views in 90 days
  - Time to qualify: 2-4 months with consistent posting

Revenue projection (finance niche):
  Month 1-2:  Building catalog, 0 revenue (not yet monetized)
  Month 3:    Hit 1K subs + 4K watch hours β†’ monetization ON
  Month 4-6:  10K-50K views/month β†’ $100-$625/month
  Month 7-12: 50K-200K views/month β†’ $625-$5,000/month
  Year 2:     200K-500K views/month β†’ $2,500-$12,500/month

Seasonal adjustments (finance):
  Q1 (Jan-Mar): RPM $8-$15 (post-holiday budget reset)
  Q2 (Apr-Jun): RPM $12-$18 (tax season advertising)
  Q3 (Jul-Sep): RPM $10-$14 (summer slump)
  Q4 (Oct-Dec): RPM $18-$30+ (holiday + year-end spending)

Strategy: Post MORE content in Q4 when RPM is highest.
          Use Q1 to build catalog for Q4 search traffic.

Layer 2: TikTok Creator Rewards

Requirements:
  - 10K+ followers
  - 100K+ views in last 30 days
  - Videos must be 1+ minute
  - Original content only

Revenue:
  $0.40-$2.00 per 1K qualified views
  "Qualified" = unique, from FYP, 5+ seconds watched, not promoted

Projection:
  100K views/month: $40-$200
  500K views/month: $200-$1,000
  1M views/month: $400-$2,000

Strategy: Focus on 60-90 second videos (minimum 1 minute for eligibility)
          Higher retention = higher RPM within the program

Layer 3: Content Rewards

Content Rewards pays creators to make UGC-style content for brand campaigns. It is performance-based: more views = more money.

How it works:
  1. Browse active campaigns on contentrewards.com
  2. Create content matching the campaign brief
  3. Post to TikTok/Instagram with campaign requirements
  4. Get paid per 1K views ($1-$2.50 RPM)

Revenue:
  No audience needed β€” payment is per view, not per follower
  10 campaigns x 5K views each = 50K views = $50-$125/month

Strategy: Low-effort supplement. Use same video pipeline.
          Some campaigns align with your niche (finance apps, side hustle tools).
          Cherry-pick campaigns that match your brand.
Finance affiliate programs:
  - NerdWallet referrals: $5-$50 per sign-up
  - Credit card offers: $50-$200 per approval
  - LLC formation services (LegalZoom, Northwest): $20-$100 per sign-up
  - Tax software (TurboTax, H&R Block): $15-$40 per sale
  - Business bank accounts: $25-$100 per account opened

Placement:
  - YouTube description (first 3 lines are visible without clicking "more")
  - TikTok bio link (link-in-bio tool: Linktree, Stan Store)
  - Instagram bio link
  - Pinned comments

Projection:
  1% click-through on 50K monthly views = 500 clicks
  5% conversion on clicks = 25 sales
  Average $40 per sale = $1,000/month

Strategy: Every video naturally leads to a recommendation.
          "Link in the description for the calculator/tool/service"
          Never hard-sell in the video itself β€” the CTA is soft and natural.

Layer 5: Sponsor Integrations

Finance niche sponsor rates:
  - 10K subscribers: $200-$500 per integration
  - 50K subscribers: $500-$2,000 per integration
  - 100K subscribers: $2,000-$5,000 per integration
  - 500K subscribers: $5,000-$15,000 per integration

Common sponsors for finance channels:
  - Fintech apps (Robinhood, SoFi, Webull)
  - Tax services
  - LLC formation services
  - Business tools (QuickBooks, FreshBooks)
  - Educational platforms

Strategy: Don't seek sponsors until 25K+ subscribers.
          Focus on AdSense and affiliate until then.
          When sponsors come, integrate naturally into content.
          Never let sponsors dictate content β€” it kills the audience.

Layer 6: Funnel to Owned Products

This is where the real leverage lives. Every video is a top-of-funnel entry point to products you own.

Owned products for finance niche:
  - Calculator tools (free, drives traffic, builds email list)
  - Spreadsheet templates ($5-$29 on Gumroad/Etsy)
  - Online courses ($49-$499)
  - eBooks/guides ($9-$29)
  - Consulting/coaching ($100-$500/hour)
  - SaaS tools ($9-$49/month)

Example funnel:
  TikTok video: "The LLC Cost Trap" (47K views)
  β†’ Link in bio: "Free LLC Cost Calculator"
  β†’ Calculator page: 2,000 visitors
  β†’ Email capture: 400 signups (20% conversion on free tool)
  β†’ Email sequence: "LLC Tax Handbook" ($19)
  β†’ Purchases: 40 sales ($760)

  One video β†’ $760 in product sales, plus AdSense, plus affiliate, plus Content Rewards

The $10K/Month Math

Conservative scenario (Month 12, single channel):

YouTube AdSense (long-form):    $3,000  (200K views x $15 RPM)
TikTok Creator Rewards:         $400    (400K views x $1.00 RPM)
Content Rewards:                $100    (50K views across campaigns)
Affiliate links:                $1,200  (30 conversions x $40 avg)
Owned products:                 $800    (40 sales x $20 avg)
────────────────────────────────────────
Total:                          $5,500/month

Aggressive scenario (Month 18, two channels):

YouTube AdSense (2 channels):   $6,000  (400K views x $15 RPM)
TikTok Creator Rewards:         $800    (800K views x $1.00 RPM)
Content Rewards:                $200    (100K views)
Affiliate links:                $2,000  (50 conversions x $40 avg)
Sponsor integration:            $1,500  (1 per month at 50K subs)
Owned products:                 $1,500  (75 sales x $20 avg)
────────────────────────────────────────
Total:                          $12,000/month

Production cost:                $200/month
Net profit:                     $11,800/month (98% margin)

YouTube AI Content Policy (July 2025)

YouTube’s most significant policy change for AI content creators went into effect July 15, 2025. Understanding it is mandatory.

What changed:
  - "Inauthentic content" rule targets mass-produced, repetitive AI content
  - YouTube Partner Program now requires content to be "significantly original
    and authentic" to remain eligible for monetization
  - Mandatory disclosure for altered/synthetic content via YouTube Studio
  - In January 2026, YouTube wiped 4.7 billion views across 16 channels
    (35M combined subscribers) in a single enforcement wave

What is ALLOWED:
  - AI-assisted content with genuine editorial oversight
  - AI-generated scripts rewritten/refined by a human
  - AI voiceover with human-selected content and structure
  - AI-generated visuals combined with original commentary/analysis

What gets DEMONETIZED:
  - Mass-produced AI content with no original insight
  - Repetitive videos that are essentially the same template with swapped data
  - Pure AI narration reading facts with no editorial layer
  - Content that doesn't add "substantial value" beyond what AI generated

How to stay compliant:
  1. ALWAYS use the "altered content" disclosure in YouTube Studio
  2. Add genuine editorial value: analysis, opinions, context
  3. Vary your templates β€” don't use the same layout for every video
  4. Include original research or unique data points
  5. Human reviews and modifies every script before rendering
  6. Each video should feel intentionally crafted, not factory-produced

The β€œReused Content” Flag

This is separate from the AI policy. YouTube flags "reused content" when:
  - Multiple channels post the same or very similar content
  - Content is compiled from other sources without transformation
  - Videos are cookie-cutter templates with only data swapped

How to avoid it:
  - Unique visual templates per channel (different color schemes, fonts)
  - Unique intro patterns (don't start every video the same way)
  - Mix video styles: explainers, comparisons, deep-dives, breakdowns
  - Original analysis β€” don't just restate what other videos say
  - Your voice/TTS voice should be consistent per channel (builds identity)

FTC Compliance for Finance Content

Required disclosures:
  1. "This is not financial advice" β€” in video description AND spoken/shown
  2. Affiliate links β€” must disclose: "Some links below are affiliate links"
  3. Sponsored content β€” must disclose: "This video is sponsored by [brand]"
  4. Material connections β€” any payment, free product, or relationship

Placement:
  - Video: spoken disclosure within first 30 seconds for sponsored content
  - Description: first 3 lines (visible without clicking "more")
  - Pinned comment: additional disclosure for affiliate links

Penalties:
  - FTC maximum civil penalty: $53,088 per violation (2025)
  - Platform demonetization
  - Channel termination for repeat violations

Finance-specific rules:
  - Never claim to be a CPA, tax attorney, financial advisor (unless you are)
  - Never guarantee returns or specific outcomes
  - Always include "consult a qualified professional" language
  - No specific investment recommendations without proper licensing

Platform-Specific Content Policies

PlatformKey RiskMitigation
YouTubeAI content demonetizationEditorial layer, disclosure, varied templates
YouTubeReused content flagUnique visuals per channel, original analysis
TikTokCommunity guidelines (finance misinformation)Factual claims only, disclaimers, sources
TikTokCreator Rewards clawbackNo bought views, no engagement pods, organic only
InstagramReach throttling for reposted contentRender separate copies, no TikTok watermark
All platformsCopyright strikes (music/footage)Pexels/Pixabay only (CC0), royalty-free music

The Credential Rule

This is critical and non-negotiable.

NEVER:
  - Claim to be a CPA, EA (Enrolled Agent), tax attorney, or financial advisor
  - Use titles like "tax expert" or "finance professional" unless verifiable
  - Imply that your AI-generated analysis is professional advice
  - Display fake credentials, certifications, or affiliations

ALWAYS:
  - Present content as "educational" and "informational"
  - Include "not financial/tax/legal advice" disclaimer
  - Recommend viewers "consult a qualified professional"
  - Be transparent that analysis is research-based, not professional opinion

Why this matters:
  One channel got sued for $1.26M for implying credentials they didn't have.
  The FTC specifically targets finance creators for credential claims.
  AI-generated content that claims authority it doesn't have is a fast path
  to legal trouble.

This is the step-by-step process for producing your first video today. No automation, no agents β€” just you and the tools.

Manual process:
  1. Open TikTok β†’ search "money" or "business" β†’ sort by "This week"
  2. Note the top 5 videos: what topics? what hooks? what format?
  3. Open YouTube Trending β†’ filter by "Education" or "Howto & Style"
  4. Cross-reference: topics appearing on BOTH TikTok and YouTube = high signal
  5. Pick one topic that connects to your niche

Example finding:
  TikTok trending: "Things that cost more than you think"
  YouTube trending: "Hidden costs of starting a business"
  Your angle: "The LLC Cost Trap Nobody Talks About"
  Connection: Your LLC calculator tool / content

Step 2: Write the Script (20 minutes)

Use the prompt chain from Section 3:
  1. Run Step 1 prompt (fact retrieval) β†’ get 8-12 facts with numbers
  2. Run Step 2 prompt (hook generation) β†’ get scroll-stopping opening
  3. Run Step 3 prompt (body + pacing) β†’ get scene-by-scene script

Review checklist:
  β–‘ Hook in first 1 second (specific number or provocative claim)
  β–‘ No filler words ("hey guys", "so basically", "alright")
  β–‘ Every sentence has ONE idea
  β–‘ Specific numbers throughout ($3,200, not "thousands")
  β–‘ Total word count: 130-160 words (for 60-second short-form)
  β–‘ CTA is natural, not salesy
  β–‘ No expert claims or financial advice language

Step 3: Gather Stock Footage (10 minutes)

Process:
  1. Extract stock keywords from each scene (from script output)
  2. Search Pexels.com or Pixabay.com for each keyword
  3. Download 6-8 clips (portrait/vertical preferred for short-form)
  4. Each clip: 5-10 seconds, matching the scene topic

Search tips:
  - Add "dark" to keywords for finance aesthetic (e.g., "dark office")
  - "Money" + specific context (e.g., "money counting", "money calculator")
  - Avoid clips with identifiable faces (licensing issues)
  - Download at highest available resolution

Step 4: Generate TTS Audio (5 minutes)

pip install edge-tts
edge-tts --voice "en-US-GuyNeural" --rate "+10%" \
  --text "Filing an LLC costs fifty dollars..." \
  --write-media narration.mp3

curl -X POST "https://api.elevenlabs.io/v1/text-to-speech/pNInz6obpgDQGcFmaJgB" \
  -H "xi-api-key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Filing an LLC costs fifty dollars...", "model_id": "eleven_multilingual_v2"}' \
  --output narration.mp3

Step 5: Render the Video (5-15 minutes)

ffmpeg -f concat -safe 0 -i clips.txt \
  -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,\
       eq=brightness=-0.15:contrast=1.2" \
  -c:v h264_nvenc -preset p4 -t 60 background.mp4

ffmpeg -i background.mp4 \
  -vf "drawtext=fontfile=Inter-Black.ttf:text='THE LLC TRAP':fontcolor=white:\
       fontsize=72:x=(w-text_w)/2:y=h*0.15:enable='between(t,0,3)'" \
  -c:a copy with_text.mp4

ffmpeg -i with_text.mp4 -i narration.mp3 \
  -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 -shortest final.mp4

curl -X POST "https://api.creatomate.com/v2/renders" \
  -H "Authorization: Bearer YOUR_CREATOMATE_KEY" \
  -H "Content-Type: application/json" \
  -d @renderscript.json

Step 6: Review (5 minutes)

Quality check:
  β–‘ Duration: 60-90 seconds (for TikTok Creator Rewards eligibility)
  β–‘ Hook: first 1 second grabs attention
  β–‘ Audio: clear, no clipping, consistent volume
  β–‘ Visuals: dark aesthetic, text readable, no awkward clip transitions
  β–‘ Numbers: all on-screen numbers match narration
  β–‘ CTA: visible and natural
  β–‘ No expert claims or financial advice language
  β–‘ Disclaimer: "Not financial advice" in planned description

Step 7: Post (5 minutes)

TikTok:
  - Upload MP4
  - Title: hook line from script
  - Hashtags: 5-8 (2 broad + 3 niche + 2 trending)
  - Add trending sound at 5% volume (optional but helps)
  - Description: 1-2 sentences + hashtags + disclaimer

YouTube Shorts:
  - Upload same MP4
  - Title: SEO-optimized (include primary keyword)
  - Description: full description with links, disclaimer, hashtags
  - Tags: 10-15 relevant tags

Instagram Reels:
  - Upload MP4 (ensure no TikTok watermark)
  - Caption: engagement question + relevant hashtags
  - Custom cover image

Step 8: Measure (48 hours later)

Metrics to track:
  - Views in first 24 hours (TikTok: >500 = good, >5K = great, >50K = viral)
  - Average watch time (>50% = script is working)
  - Engagement rate (likes + comments + shares / views)
  - Click-through to bio link (if tracking)
  - Profile visits (proxy for brand awareness)

Decision tree:
  Views > 10K β†’ Topic works. Make a long-form version.
  Views 1K-10K β†’ Format works but hook needs improvement. Iterate.
  Views < 1K β†’ Topic or hook failed. Try different angle.
  Views < 100 β†’ Something is wrong (shadow ban, posting time, or account issue)

Total Time: ~65 minutes for your first video

After the first 3-5 videos, this drops to 20-30 minutes as you build templates and muscle memory. After automation (Section 9), it drops to 5-10 minutes of review time per video.


Video Rendering API Comparison

FeatureCreatomateShotstackRemotionFFmpeg (local)
TypeCloud APICloud APIReact frameworkCLI tool
Cost~$0.20-$0.50/min rendered$0.20-$0.30/min renderedFree (individuals), $0.01/render (business)Free
TTS Built-inYes (ElevenLabs integrated)No (bring your own audio)No (bring your own audio)No
CaptionsAuto-synced from voiceoverManual positioningProgrammatic (React components)Manual (drawtext filter)
Template EditorYes (visual + JSON)Yes (visual + JSON)Code-only (React)Code-only (CLI)
QualityProduction-gradeProduction-gradeProduction-gradeDepends on effort
Latency2-10 min per render2-10 min per render1-5 min (local), 2-10 min (Lambda)30s-2min (GPU)
ScalingUnlimited (cloud)Unlimited (cloud)Lambda or local fleetLimited by hardware
Learning CurveLow (JSON templates)Low (JSON timelines)Medium (React + video concepts)High (FFmpeg filters)
Best ForQuick start, TTS integrationTimeline-based editingCustom animations, React devsZero-cost, maximum control
Docscreatomate.com/docsshotstack.io/docsremotion.dev/docsffmpeg.org/documentation

TTS Provider Comparison

Featureedge-ttsElevenLabsGoogle Cloud TTSAmazon Polly
CostFree$0.30/1K chars ($5-$99/mo)$4-$16/1M chars$4-$16/1M chars
QualityMediumPremiumHighHigh
Voices300+1000+ (cloning available)400+60+
Latency1-3s2-5s1-2s1-2s
Languages45+29+40+30+
API KeyNot requiredRequiredRequiredRequired
Best ForTesting, TikTok, zero budgetYouTube, premium qualityEnterprise, batchEnterprise, AWS stack
GotchaUnofficial API, may breakExpensive at scalePer-character billingFewer voice options

Video Clipping Tool Comparison (Long-to-Short)

FeatureOpusClipVizardManual FFmpegDIY (prompt + render)
Cost$15-$29/mo$14.50/moFreeFree
AI ClippingYes (ClipAnything)YesNoPrompt-based
Virality ScoreYes (paid only)NoNoNo
Caption Quality97%+ accuracyHighManualVia Creatomate
Batch ProcessingYesYesScript-basedScript-based
Platform ExportAll majorAll majorManualManual
Best ForNon-technical creatorsBudget-consciousFull controlDevelopers with pipeline

Trend Intelligence Comparison

FeatureVirloTikTok Creative CenterYouTube Trending APIManual Discovery
Cost$49-$199/moFreeFree (quota)Free (time cost)
CoverageTikTok + YT + IGTikTok onlyYouTube onlyMulti-platform
APIYes (REST)NoYesNo
Early Signal12-24h before mainstreamReal-time6-12h delay2-4h delay
Niche FilteringYesLimitedCategory onlyManual
Best ForAutomated pipeline, speedManual researchYouTube focusGetting started
Docsvirlo.ai/developersads.tiktok.comdevelopers.google.comN/A

Full Pipeline Cost Comparison

PipelineMonthly CostVideos/MonthCost/VideoQualityAutomation
Zero Budget (edge-tts + FFmpeg + manual)$030$0MediumNone
Starter (edge-tts + FFmpeg + scripts)$20 (OpenAI API)100$0.20MediumPartial
Growth (ElevenLabs + Creatomate)$50-$100100$0.50-$1.00HighMostly
Scale (ElevenLabs + Creatomate + Virlo)$200-$400300+$0.67-$1.33HighFull
Premium (ElevenLabs + Remotion + custom)$100-$300200+$0.50-$1.50PremiumFull

These are real mistakes that kill channels. Every one of them has been observed in the wild.

Don’tDo InsteadWhy
Start every video with β€œHey guys, welcome back”Start with a specific number or provocative claimFirst-word retention: β€œHey” = 35% drop. β€œ$3,200” = 5% drop.
Use the same template for every videoVary layouts, color accents, and intro patternsYouTube flags identical templates as β€œreused content” and demonetizes
Auto-post without human reviewGenerate with AI, approve with human eyesOne factual error, one compliance violation, one embarrassing mistake can destroy a channel permanently
Claim credentials you don’t haveSay β€œresearch shows” or β€œbased on IRS data”FTC penalties up to $53,088 per violation. One creator fined $1.26M.
Post the same video to all platforms simultaneouslyStagger: TikTok first, then YT Shorts (4h later), then IG Reels (24h later)Algorithms penalize duplicate content detected across platforms
Use trending sounds at full volumeLayer trending sound at 5% volume under your TTSTikTok algorithm boosts trending sounds, but the sound should not overpower your narration
Optimize for views onlyOptimize for retention (average view duration)High views with low retention = algorithm stops promoting. High retention = exponential growth.
Use approximations (β€œabout $3,000”)Use exact numbers (β€œ$3,247”)Specific numbers signal authority. Round numbers signal guessing.
Skip financial disclaimersInclude β€œNot financial advice” in description AND spokenFTC is actively auditing finance creators. One missed disclaimer = one violation.
Mass-produce 50 low-quality videos/dayProduce 5-10 quality videos/day with editorial layerYouTube’s July 2025 policy explicitly targets β€œmass-produced” AI content
Use TikTok watermark on InstagramRender separate copies or remove watermark before cross-postingInstagram throttles reach for TikTok-watermarked content
Fade in from black as your openingStart in media res: first frame = contentFade-in = 2-3 seconds of nothing = viewer scrolls away
Put links in TikTok video (they don’t work)Use β€œlink in bio” CTA and optimize your bio link pageTikTok videos don’t support clickable links; bio link is the only path
Ignore Q4 seasonalityDouble your posting cadence October-DecemberFinance RPM is 1.5-2x higher in Q4. Same content, twice the revenue.
Generate long-form videos without chaptersAdd chapters every 2-3 minutes in descriptionChapters improve SEO, user experience, and YouTube’s understanding of your content
Use copyrighted music or footageUse only Pexels, Pixabay (CC0), or royalty-free librariesOne copyright strike = channel warning. Three = channel termination.

Official Documentation

Trend Intelligence

Platform Policies and Monetization

RPM and Earnings Data

TTS and Voice Tools

Video Clipping and Repurposing

Automation and Programmatic Video

Faceless Channel Strategy

Stock Footage and Media APIs


This article reflects the state of platforms, APIs, and policies as of March 2026. Platform policies, RPM rates, and API pricing change frequently. Always verify current terms before launching a production pipeline.


Edit page
Share this post on:

Previous Post
Video Production Techniques
Next Post
Cost Observability for Cloudflare Workers