# /motion-ad-generator

> Generates a fully finished, export-ready branded video ad using Creatomate — no CapCut step, no manual editing.

# Skill: `/motion-ad-generator`

## Overview

Generates a fully finished, export-ready branded video ad using Creatomate — no CapCut step, no manual editing. Canva-style: animated text, brand images (Krea Nano Banana), ElevenLabs voiceover, background music, brand colors.

**What this skill produces:**
- Finished MP4 (1080×1920, 9:16) — ready to upload directly to Meta/TikTok
- ElevenLabs voiceover (Croatian/German/English)
- 2–3 brand images (Krea Nano Banana)
- All files in Google Drive + Slack report

**What this skill does NOT do:**
- Generate AI video footage (use `/video-ad-generator --style cinematic` for that)
- Require any template setup — Claude writes the full composition JSON per render

**When to use:**
- Fast-turnaround branded ads (same day delivery)
- Offer announcements, promotions, new service launches
- Variants of existing campaigns (change headline/CTA, keep brand colors)
- When client doesn't need cinematic footage — just wants a clean branded video

**Trigger:**
- `/motion-ad-generator ClientName`
- `/motion-ad-generator ACME Agency --brief "mini implant offer, 20% popust, seniors"`

---

## Critical Files

- `ACME Agency/scripts/motion_ads_generate.mjs` — pipeline script
- `ACME Agency/scripts/lib/creatomate.mjs` — Creatomate API wrapper + buildSource()
- `ACME Agency/scripts/lib/elevenlabs.mjs` — ElevenLabs TTS
- `ACME Agency/scripts/lib/krea.mjs` — Krea image generation (Nano Banana)
- `ACME Agency/scripts/lib/google_drive.mjs` — Drive upload
- `ACME Agency/scripts/lib/slack.mjs` — Slack reporting
- `ACME Agency/clients/clients.json` — client registry
- `ACME Agency/clients/<ClientName>/brand-dna.md` — brand context
- `ACME Agency/clients/<ClientName>/motion-ads/<campaign>/script.json` — Claude-written script

---

## Script Format

Write this to `ACME Agency/clients/<ClientName>/motion-ads/<campaign-slug>/script.json` **before running the pipeline**:

```json
{
  "client": "ClientName",
  "campaign": "<id>",
  "adType": "motion-graphics",
  "targetDuration": 20,
  "voScript": "Pune 48 riječi za 20 sekundi. Kalibriraj prema formuli: targetDuration × 2.4 = broj riječi.",
  "headline": "Kratki, udarni naslov — max 6 riječi",
  "subheadline": "Benefit ili specifikacija (opcionalno)",
  "cta": "Zatraži besplatnu konzultaciju",
  "brandColors": {
    "primary": "#1A4F8A",
    "accent": "#D59C44",
    "text": "#FFFFFF"
  },
  "musicMood": "professional",
  "imagePrompts": [
    "Modern dental clinic interior, Croatia, bright professional photography, no people, wide shot, 9:16 vertical",
    "Close-up of healthy white teeth and warm smile, soft studio light, authentic Croatian aesthetic",
    "Titanium mini implant screws macro photography, gold accent lighting, clean white background"
  ]
}
```

**Campaign slug:** `<keyword>-<offer/audience>-<YYYY-MM>` e.g. `<id>`

---

## VO Calibration — CRITICAL

Same rule as cinematic pipeline:
```
ElevenLabs Croatian ≈ 2.3–2.5 words/second
Formula: targetDuration × 2.4 = target word count
Example: 20s = 48 words | 25s = 60 words | 30s = 72 words
Count words BEFORE saving script.json. Mismatch = VO ends before video.
```

---

## Music Moods

| Mood | Use when |
|------|----------|
| `professional` | Clinics, B2B, services, authority brands |
| `energetic` | Fitness, youth audience, product reveals |
| `warm` | Lifestyle, family, wellness, emotional appeal |
| `emotional` | Testimonials, transformation stories |
| `neutral` | Luxury, real estate, minimalist brands |

---

## Image Prompts

Write **2–3 prompts**. Each image fills the upper ~55% of the 9:16 frame for one phase of the video (hook → benefit → CTA). No text needed in images (text is added by Creatomate). Tips:
- Specify `9:16 vertical` in each prompt
- Include brand setting (clinic, office, product closeup, lifestyle)
- Avoid people's faces when possible (consistency issue across slides)
- Think: what visual reinforces the headline? → that's image 1

---

## Preflight (run BEFORE any Krea/ElevenLabs/Creatomate call)

Motion ads compose multiple paid services (Krea images + ElevenLabs VO + Creatomate render). One missing piece = whole pipeline wasted. Validate all of them upfront.

1. **Client exists** in `clients.json`. Resolve canonical key.
2. **Required env vars**: `KREA_API_KEY`, `ELEVENLABS_API_KEY`, `CREATOMATE_API_KEY`. If any missing → abort, name them.
3. **Script JSON has all required fields**: `campaign`, `headline`, `cta`, `voScript`, `imagePrompts[]`, `brandColors`. Missing any → abort.
4. **`imagePrompts[]` length is 1-6** (rendering more is wasted; rendering 0 = no visuals).
5. **`voScript` word count matches `targetDuration`** per the calibration table in this SKILL.md (`## VO Calibration`). If mismatch → abort with the expected range.
6. **`musicMood` is one of**: `professional`, `energetic`, `warm`, `emotional`, `neutral`. Reject anything else.
7. **`brandColors.primary` and `brandColors.accent`** are valid hex (`#your-channel`). Required by Creatomate templates.
8. **Croatian/German diacritics check** in `voScript` — warn if you see `s/z/c/d` where `š/ž/č/ć/đ` should be (ElevenLabs needs correct chars).
9. **`drive_folder_id` reachable** if `--drive` enabled.
10. **Slack channel resolves** if reporting enabled.
11. **Creatomate image transport:** images are inlined as `data:image/jpeg;base64,...` URIs via `toDataUri()` (from `lib/creatomate.mjs`). No external host required. catbox.moe was the prior dependency — it's now HTTP 412 banned (2026-05-26) and 0x0.st is 503 indefinitely. See [<id>.md](../../../memory/<id>.md).

If all checks pass, log "preflight: OK (n images, voscript=Xs, music=mood, est cost=...)" and proceed.

---

## Workflow

### Step 0 — Client lookup

Read `ACME Agency/clients/clients.json`. Extract:
- `drive_folder_id` — Drive upload target
- `slack_channel` — Slack report target
- `elevenlabs_voice_id` — default voice (use if set)
- `market` — language context (Croatia, Germany, etc.)

---

### Step 1 — Brand research

Check for `ACME Agency/clients/<ClientName>/brand-dna.md`. Need at minimum:
- Primary hex color, accent hex color
- Brand tone (for music mood selection)
- Language

If brand-dna.md doesn't exist: scrape website via Firecrawl, extract colors and tone, write brand-dna.md.

---

### Step 2 — Brief + script.json

**If `--brief` was provided:** use it directly.

**If not, ask 4 questions:**
1. What's this campaign about? (offer, product, key message)
2. Target audience?
3. Duration? (15s / 20s / 30s) — default: 20s
4. Music mood? (show the mood table) — default: professional

**Write script.json** to `motion-ads/<campaign>/script.json`.

**Show user a summary before running:**
```
Campaign:  <id> | Duration: 20s | Music: professional
Headline:  "Bezbolni mini implantati — za samo 2 sata"
Subheadline: "Bez rezanja. Bez opće anestezije."
CTA:       "Zatraži besplatnu konzultaciju"
VO:        48 words ✓ (20s × 2.4)
Images:    3 prompts
Proceed?
```

---

### Step 3 — Run the pipeline

```bash
node ACME Agency/scripts/motion_ads_generate.mjs "ClientName" \
  --script "ACME Agency/clients/ClientName/motion-ads/<campaign>/script.json" \
  [--voice <voice_id_or_name>] \
  [--no-slack] [--no-drive]
```

**Pipeline phases:**
- **Phase A** — ElevenLabs → voiceover.mp3
- **Phase B** — Krea Nano Banana → image-01.jpg, image-02.jpg, image-03.jpg
- **Phase C** — All assets uploaded to Drive → public URLs
- **Phase D** — Creatomate renders full MP4 → downloaded → uploaded to Drive
- **Phase E** — Slack report with Drive link + feedback thread

---

## Output

```
ACME Agency/clients/<ClientName>/motion-ads/<campaign>/
├── script.json
├── voiceover.mp3
├── image-01.jpg
├── image-02.jpg
├── image-03.jpg
├── final-ad.mp4        ← finished, upload-ready
└── manifest.json

Drive: <ClientFolder>/Motion Ads/<Year>/<Month>/<campaign>/
```

## Verification (run AFTER pipeline completes — confirm the MP4 actually shipped)

Check ALL of these before declaring done:

- [ ] `final-ad.mp4` exists locally AND file size > 500 KB
- [ ] `voiceover.mp3` exists locally AND duration ≈ targetDuration (within ±15%)
- [ ] Image count locally = `imagePrompts.length` (no silent Krea failures dropping images)
- [ ] Each image > 50 KB on disk
- [ ] If `--drive`: `final-ad.mp4`, `voiceover.mp3`, all images uploaded to Drive `Motion Ads / <Year> / <Month> / <campaign>/` AND public URLs in manifest
- [ ] `manifest.json` records: campaign, duration, voice ID, music mood, image count, Creatomate render ID
- [ ] Slack post via slack-reporter returned `ts` non-null
- [ ] Creatomate render status = `succeeded` (not `failed` or `pending` swallowed)
- [ ] No "black screen" failure pattern (verify by checking final MP4 is non-trivial size + Creatomate response had `url` field)

If any check fails, name the gap explicitly. Most common Creatomate failure mode is silent black screen — check video file size as the canary.

---

## Tips

- **Test locally first:** `--no-slack --no-drive` to check VO and images without Drive upload or Creatomate render
- **Voice not right?** Run `--list-voices` then pass `--voice <id>` explicitly
- **Re-render only:** if images and VO are already uploaded, you can re-run just the Creatomate step by passing the existing public URLs directly in the source JSON via `buildSource()`
- **Croatian diacritics:** always verify š, đ, č, ž, ć in voScript — ElevenLabs needs correct diacritics for proper pronunciation
- **Cost per video:** ~$0.27 Creatomate + ~$0.10 Krea images + minimal ElevenLabs = under $0.50/video
