# ai-video-ads

Adopt this skill: https://proskills.md/skills/other/nova0x2-ai-film-producer-skill-ai-video-ads

Page: https://proskills.md/skills/other/nova0x2-ai-film-producer-skill-ai-video-ads
This document is the public skill bundle for that page. It contains SKILL.md and every helper file the catalog carries. No login is required.

## File: ai-video-ads/SKILL.md
size: 9227
sha: 2e1273564ff5e11e37c0887ddb9dce04136d7fb4
upstream: https://github.com/nova0x2/ai-film-producer-skill/blob/main/ai-video-ads/SKILL.md

````text
---
name: ai-video-ads
description: An autonomous AI film producer. Given only a product (name, link or photo), Claude researches it, writes a catchy ~60 s ad script, designs the cast, product and locations, generates every image and clip itself in Google Flow and Grok Imagine (driven in the user's Chrome) or Higgsfield/Kling MCP, QC's each clip for drift, makes the voiceover, and cuts the final film with ffmpeg plus a CapCut kit — photoreal and consistent like the reference reels (Oraimo story ad, NorthQuest UGC ad, Pocari Sweat mini-film). Use whenever the user hands over a product or asks for an AI ad, AI commercial, product video, concept ad, brand film, AI short film, or "a video like this" — even if they name no tool.
---

# AI Video Ads — autonomous producer

The user gives a product. Claude does everything else: research → concept → script → production
bible → every image → every clip → QC → voice, music, graphics → ffmpeg edit → delivery.
Quality bar: `references/reference-reels.md` .

Rule zero: **nothing is animated until it exists as an approved still.** Consistency is
decided at the image stage; the video model only adds motion.

Only stop to ask the user when: a step costs more credits than the approved budget, a site needs
him to sign in, a download needs his OK (ask once per project, listing what will be saved), or
the music choice needs licensing. Everything else is Claude's call — decide, log it, move on.

Project folder: `~/Movies/AI Ads/<product>-ad/`
```
research/   brief.md  (facts, audience, competitors, ad references, product photos)
script.md   shotlist.md   bible.md   jobs.md (every prompt + tool + result, per shot)
assets/     character/  product/  locations/
keyframes/  clips/  audio/  gfx/  edl.json  final/  final/capcut-kit/
```

## Phase 1 — Research (≈15 min, free)

1. WebSearch + WebFetch: official site, specs, price, colours, packaging, tagline, the brand's
   own claims (an allowlist — never invent stronger ones), target customer, where it's sold.
2. Find 3–5 existing ads or creator videos for the product or its category (YouTube, IG,
   TikTok); watch the best 2 with `/watch` and note hook, pacing, shots, what's missing.
3. Collect real product images (official site, store listings) into `research/product/` —
   used only as references so the label and shape come out right. Need ≥1 clean front shot,
   ideally side/back/logo close-up.
4. Write `research/brief.md`: one-line product truth, audience, the single benefit to sell,
   the feeling, local setting (default Lagos/Nigeria unless the brand says otherwise), CTA.

## Phase 2 — Concept and 60 s script

Write 3 one-line concepts, pick the strongest yourself (most visual, most surprising, one
clear benefit), note why in `script.md`. Every concept must have a tiny story or running
gag — not a feature list.

60 s structure (~20–26 shots of 1.5–4 s):

| Time | Beat | Job |
| --- | --- | --- |
| 0–3 s | Hook | Odd image, bold line or question. Decides the whole ad. |
| 3–12 s | Problem / world | Character, situation, the itch the product scratches |
| 12–35 s | Product in action | Product enters; 2+ macro inserts; benefit shown, not said |
| 35–50 s | Payoff / twist | Emotional win, the gag pays off, proof moment |
| 50–60 s | Brand + CTA | Product hero shot, end card with name, tagline, CTA |

Voice: either a voiceover (~130–150 words for 60 s, short punchy lines) or 3–6 short
dialogue lines spoken on camera, plus music. Write the script as a two-column table: VISUAL |
AUDIO, with timings that add up to 60 s.

## Phase 3 — Production bible (`bible.md`)

- **Style anchor** (one line pasted into every prompt), e.g. "photoreal commercial, 35mm,
  soft natural window light, shallow depth of field, warm tones, unretouched skin texture".
- **Cast**: identity block per character (age, skin, face shape, hair with exact style,
  build) + wardrobe per scene. Original people only.
- **Product**: exact description (shape, colours, materials, label text as printed).
- **Locations**: one line each + lighting state.
- **Shot list** (`shotlist.md`): #, seconds, size, angle/POV, camera move, cast, product
  state, location, action, audio, tool. Neighbouring shots never share POV and distance band;
  one action + at most one camera move per shot; ≥2 product macros; one unusual POV.
- **Credit budget**: shots × takes × cost, per tool, per day. Get the user's OK once.

## Phase 4 — Lock the assets (images)

Method: Higgsfield `character-sheet` workflow rules (slot order, "photoreal-unretouched"
realism module, mature adult structure, split-screen full body + close-up, negative tail).

1. **Character**: 4 portraits → choose the best → split-screen sheet + turnaround from it.
2. **Product**: upload the real photo; make a product sheet (front/side/back/logo macro) by
   *editing* that photo with Nano Banana Pro. Logo must match letter for letter.
3. **Locations**: empty plates, no people.
4. **Keyframes**: for every shot, compose its first frame (character + product + location
   references) as a still; for reveals also the last frame. Inspect each; fix with image edits.

Save everything locally in `assets/` and `keyframes/` and log prompts in `jobs.md`.

## Phase 5 — Generate the clips

Pick per shot (see `references/browser-tools.md` for the exact screens and clicks):

| Shot type | Tool |
| --- | --- |
| Hero product shots, reveals, first→last frame moves | Flow, Veo 3.1 Quality / Fast, Frames |
| Recurring character in new compositions, dialogue + lip-sync | Flow, Omni 1.1 Flash with Characters + voice (Ingredients) |
| Cheap drafts, 10–15 s single takes, simple motion | Grok Imagine, image-to-video, 720p |
| Many fast consistent cuts from one board (if Higgsfield has credits) | Higgsfield Seedance 2.5 `omni_reference` (method below) |

Video prompt order: shot size + camera move → subject + ONE action → dialogue in quotes →
setting → lighting → style anchor → Audio: line (ambience, SFX, "no music") → locks ("same
face, hair, outfit and product as the reference; no on-screen text"). Draft cheap (Omni 360p,
Veo Lite, Grok 480p), finish winners at 720p/Quality.

Board method (Higgsfield, from its `ugc-video` workflow — load `references/ugc-board.md` and
`references/ugc-clip.md` with `get_workflow_bundle_file`): one 21:9 `gpt_image_2` board of
eight 9:16 panels → `seedream_v5_pro` realism cleanup → inspect → `seedance_2_5`
`omni_reference` with [board, character, product] and a "Cut 1 (0–1.9s) … Hard cut to." × 8
prompt. Gives 8 consistent cuts per generation.

Download each accepted clip to `clips/sNN_takeK.mp4`.

## Phase 6 — QC (every clip, no exceptions)

`ffmpeg -i clip -vf fps=2,scale=360:-1 qc/sNN_%02d.jpg`, tile, Read. Reject → regenerate only
that shot if any fail: face/hair/skin matches the master · outfit matches the scene ·
product shape, colour and logo exact · hands correct · no stray text/watermarks/other brands ·
speech clear and in sync · duration and 9:16 correct. Log verdicts in `jobs.md`.

## Phase 7 — Sound and graphics

- **Voiceover**: Flow custom voice (on-camera dialogue) or local Kokoro TTS
  (`~/.venvs/kokoro`, see memory `tts-voice-setup`) for narration → `audio/vo.wav`.
- **Music**: Epidemic Sound in the user's Chrome (logged in) — search genre + mood, choose by
  BPM, download full-mix MP3; land the beat drop on the first product cut. Fallback: Flow Music. Pick tempo to the cut (~100–120 BPM for lifestyle).
- **SFX**: whooshes on transitions, product sounds (cap pop, click) from the clips or a library.
- **End card + any text**: render in HyperFrames (`/hyperframes`) as a transparent MOV or
  PNG → `gfx/`. The local ffmpeg has no drawtext/libass, so never burn text with ffmpeg.
- **Captions** only for spoken lines, house style.

## Phase 8 — Edit with ffmpeg

Write `edl.json` (format in the script's docstring) and run:

```
python3 ~/.claude/skills/ai-video-ads/scripts/assemble.py edl.json --kit
```

It conforms every clip to 1080×1920 30 fps (crop-to-fill), trims in/out, speed-ramps, hard
cuts or fades, mixes clip audio + VO + SFX + music with the music ducked under the voice,
overlays graphics by time, normalises to -14 LUFS, and writes `final/` + `capcut-kit/`.

Edit rules: cut on action; trim 0.2–0.4 s of dead head/tail from AI clips; hard cuts by
default, fades only into the end card; land cuts on music beats; first frame must already be
the hook. No colour grade beyond what the shots have. Watch the export (frames at every
cut + loudness check) before calling it done.

## Phase 9 — Deliver

`final/<product>_ad_9x16.mp4`, the CapCut kit, `script.md`, and a 5-line production note
(concept, tools used, credits spent, known weak shots). Post caption with "#conceptad" when
the brand didn't commission it.

## Guardrails

- Original characters only; no real person's likeness without their consent and photos.
- Real brands = spec/concept ads: exact logo, only the brand's own claims, labelled as concept.
- No fake testimonials presented as real customers.
- Downloads and spending: confirm with the user as above; never enter passwords.
````

## File: ai-video-ads/scripts/assemble.py
size: 9379
sha: fba53c65ed9d92841e0cf301c879c514d8aacccf
upstream: https://github.com/nova0x2/ai-film-producer-skill/blob/main/ai-video-ads/scripts/assemble.py

```text
#!/usr/bin/env python3
"""Cut an AI ad film from an edit decision list (EDL) with ffmpeg.

Usage: python3 assemble.py edl.json [--kit]

EDL (JSON):
{
  "output": "final/ad_9x16.mp4",
  "width": 1080, "height": 1920, "fps": 30, "lufs": -14,
  "clips": [
    {"file": "clips/s01.mp4", "in": 0.3, "out": 2.8,
     "speed": 1.0,            # optional, 0.5-2.0
     "audio": 0.6,            # clip's own audio level (0 = mute)
     "transition": {"type": "fade", "dur": 0.25}}   # into the NEXT clip; omit = hard cut
  ],
  "voiceover": {"file": "audio/vo.wav", "start": 0.4, "volume": 1.0},
  "music": {"file": "audio/music.mp3", "start": 0, "volume": 0.35,
            "duck": true, "fade_in": 0.5, "fade_out": 2.0},
  "sfx": [{"file": "audio/whoosh.wav", "start": 12.0, "volume": 0.8}],
  "overlays": [{"file": "gfx/endcard.mov", "start": 55.0, "end": 60.0}]
}

Relative paths resolve against the EDL's folder. Overlays may be PNG or video with alpha
(e.g. a HyperFrames render). --kit also writes a CapCut kit next to the output: each
conformed clip plus separate voice / music / sfx / clip-audio stems.
"""
import json
import os
import shutil
import subprocess
import sys
import tempfile

def run(cmd):
    r = subprocess.run(cmd, capture_output=True, text=True)
    if r.returncode != 0:
        sys.exit(f"ffmpeg failed:\n{' '.join(cmd)}\n{r.stderr[-3000:]}")
    return r

def probe_duration(path):
    r = run(["ffprobe", "-v", "error", "-show_entries", "format=duration",
             "-of", "csv=p=0", path])
    return float(r.stdout.strip())

def has_audio(path):
    r = run(["ffprobe", "-v", "error", "-select_streams", "a", "-show_entries",
             "stream=index", "-of", "csv=p=0", path])
    return bool(r.stdout.strip())

def atempo_chain(speed):
    parts, s = [], speed
    while s > 2.0:
        parts.append("atempo=2.0"); s /= 2.0
    while s < 0.5:
        parts.append("atempo=0.5"); s /= 0.5
    parts.append(f"atempo={s:.4f}")
    return ",".join(parts)

def conform(clip, base, W, H, fps, out):
    src = os.path.join(base, clip["file"])
    t_in = float(clip.get("in", 0))
    t_out = float(clip.get("out", probe_duration(src)))
    speed = float(clip.get("speed", 1.0))
    vol = float(clip.get("audio", 1.0))
    vf = (f"scale={W}:{H}:force_original_aspect_ratio=increase,crop={W}:{H},"
          f"setsar=1,fps={fps},setpts=(PTS-STARTPTS)/{speed}")
    cmd = ["ffmpeg", "-y", "-ss", f"{t_in}", "-to", f"{t_out}", "-i", src]
    if has_audio(src):
        af = f"asetpts=PTS-STARTPTS,{atempo_chain(speed)},volume={vol},aresample=48000"
        cmd += ["-filter_complex", f"[0:v]{vf}[v];[0:a]{af},aformat=channel_layouts=stereo[a]",
                "-map", "[v]", "-map", "[a]"]
    else:
        cmd += ["-f", "lavfi", "-i", "anullsrc=r=48000:cl=stereo",
                "-filter_complex", f"[0:v]{vf}[v]", "-map", "[v]", "-map", "1:a", "-shortest"]
    cmd += ["-c:v", "libx264", "-preset", "medium", "-crf", "16", "-pix_fmt", "yuv420p",
            "-c:a", "pcm_s16le", out]
    run(cmd)
    return probe_duration(out)

def join(parts, durs, clips, out):
    """Hard cuts via concat; fades via xfade/acrossfade chained with computed offsets."""
    inputs = []
    for p in parts:
        inputs += ["-i", p]
    if not any(c.get("transition") for c in clips[:-1]):
        fc = "".join(f"[{i}:v][{i}:a]" for i in range(len(parts)))
        fc += f"concat=n={len(parts)}:v=1:a=1[v][a]"
    else:
        fc, vprev, aprev, t = "", "[0:v]", "[0:a]", durs[0]
        for i in range(1, len(parts)):
            tr = clips[i - 1].get("transition") or {}
            d = float(tr.get("dur", 0.0)) if tr else 0.0
            if d <= 0:  # hard cut inside a faded chain = 1-frame fade
                d = 0.034
            kind = tr.get("type", "fade") if tr else "fade"
            off = t - d
            fc += (f"{vprev}[{i}:v]xfade=transition={kind}:duration={d}:offset={off:.3f}[v{i}];"
                   f"{aprev}[{i}:a]acrossfade=d={d}[a{i}];")
            vprev, aprev, t = f"[v{i}]", f"[a{i}]", off + durs[i]
        fc = fc.rstrip(";").replace(f"[v{len(parts)-1}]", "[v]").replace(f"[a{len(parts)-1}]", "[a]")
    run(["ffmpeg", "-y", *inputs, "-filter_complex", fc, "-map", "[v]", "-map", "[a]",
         "-c:v", "libx264", "-preset", "medium", "-crf", "16", "-pix_fmt", "yuv420p",
         "-c:a", "pcm_s16le", out])
    return probe_duration(out)

def main():
    if len(sys.argv) < 2:
        sys.exit(__doc__)
    edl_path = os.path.abspath(sys.argv[1])
    kit = "--kit" in sys.argv
    base = os.path.dirname(edl_path)
    edl = json.load(open(edl_path))
    W, H, fps = edl.get("width", 1080), edl.get("height", 1920), edl.get("fps", 30)
    out = os.path.join(base, edl["output"])
    os.makedirs(os.path.dirname(out), exist_ok=True)
    tmp = tempfile.mkdtemp(prefix="assemble_")

    clips = edl["clips"]
    parts, durs = [], []
    for i, c in enumerate(clips):
        p = os.path.join(tmp, f"c{i:03d}.mkv")
        durs.append(conform(c, base, W, H, fps, p))
        parts.append(p)
    picture = os.path.join(tmp, "picture.mkv")
    total = join(parts, durs, clips, picture)

    # Audio + overlays in one pass over the joined picture.
    inputs, fc, amix, idx = ["-i", picture], [], ["[0:a]"], 1
    vo = edl.get("voiceover")
    if vo:
        inputs += ["-i", os.path.join(base, vo["file"])]
        ms = int(float(vo.get("start", 0)) * 1000)
        fc.append(f"[{idx}:a]adelay={ms}|{ms},volume={vo.get('volume', 1.0)},"
                  f"aresample=48000,asplit=2[vo][vokey]")
        amix.append("[vo]"); idx += 1
    for k, s in enumerate(edl.get("sfx", [])):
        inputs += ["-i", os.path.join(base, s["file"])]
        ms = int(float(s.get("start", 0)) * 1000)
        fc.append(f"[{idx}:a]adelay={ms}|{ms},volume={s.get('volume', 1.0)},aresample=48000[sfx{k}]")
        amix.append(f"[sfx{k}]"); idx += 1
    mu = edl.get("music")
    if mu:
        inputs += ["-i", os.path.join(base, mu["file"])]
        ms = int(float(mu.get("start", 0)) * 1000)
        fo = float(mu.get("fade_out", 2.0))
        chain = (f"[{idx}:a]atrim=0:{total:.3f},asetpts=PTS-STARTPTS,adelay={ms}|{ms},"
                 f"volume={mu.get('volume', 0.35)},afade=t=in:d={mu.get('fade_in', 0.5)},"
                 f"afade=t=out:st={max(0, total - fo):.3f}:d={fo},aresample=48000")
        if vo and mu.get("duck", True):
            fc.append(chain + "[mraw]")
            fc.append("[mraw][vokey]sidechaincompress=threshold=0.03:ratio=8:attack=20:release=350[mus]")
        else:
            fc.append(chain + "[mus]")
            if vo:
                fc.append("[vokey]anullsink")
        amix.append("[mus]"); idx += 1
    elif vo:
        fc.append("[vokey]anullsink")
    lufs = edl.get("lufs", -14)
    fc.append(f"{''.join(amix)}amix=inputs={len(amix)}:normalize=0:duration=first,"
              f"loudnorm=I={lufs}:TP=-1.5:LRA=11[aout]")
    vlast = "[0:v]"
    for k, o in enumerate(edl.get("overlays", [])):
        f = os.path.join(base, o["file"])
        if f.lower().endswith((".png", ".jpg", ".jpeg")):
            inputs += ["-loop", "1", "-i", f]
        else:
            inputs += ["-i", f]
        st, en = float(o["start"]), float(o.get("end", total))
        fc.append(f"[{idx}:v]scale={W}:{H},setpts=PTS-STARTPTS+{st}/TB[ov{k}]")
        fc.append(f"{vlast}[ov{k}]overlay=0:0:eof_action=pass:enable='between(t,{st},{en})'[vo{k}]")
        vlast = f"[vo{k}]"; idx += 1
    vmap = vlast if vlast != "[0:v]" else "0:v"
    run(["ffmpeg", "-y", *inputs, "-filter_complex", ";".join(fc), "-map", vmap,
         "-map", "[aout]", "-t", f"{total:.3f}", "-c:v", "libx264", "-preset", "slow",
         "-crf", "17", "-pix_fmt", "yuv420p", "-r", str(fps), "-c:a", "aac", "-b:a", "256k",
         "-movflags", "+faststart", out])

    # Single-pass loudnorm undershoots on short ads; measure and correct the gap.
    r = subprocess.run(["ffmpeg", "-hide_banner", "-i", out, "-af", "ebur128=framelog=quiet",
                        "-f", "null", "-"], capture_output=True, text=True)
    meas = [l for l in r.stderr.splitlines() if l.strip().startswith("I:")]
    if meas:
        gap = lufs - float(meas[-1].split()[1])
        if abs(gap) > 0.5:
            fixed = out + ".tmp.mp4"
            run(["ffmpeg", "-y", "-i", out, "-c:v", "copy", "-af",
                 f"volume={gap:.2f}dB,alimiter=limit=0.89", "-c:a", "aac", "-b:a", "256k",
                 "-movflags", "+faststart", fixed])
            os.replace(fixed, out)

    if kit:
        kdir = os.path.join(os.path.dirname(out), "capcut-kit")
        os.makedirs(kdir, exist_ok=True)
        for i, p in enumerate(parts):
            run(["ffmpeg", "-y", "-i", p, "-an", "-c:v", "copy", os.path.join(kdir, f"V{i+1:02d}.mkv")])
            run(["ffmpeg", "-y", "-i", p, "-vn", os.path.join(kdir, f"A{i+1:02d}_clip_audio.wav")])
        for key in ("voiceover", "music"):
            if edl.get(key):
                shutil.copy(os.path.join(base, edl[key]["file"]), kdir)
        for s in edl.get("sfx", []):
            shutil.copy(os.path.join(base, s["file"]), kdir)
        for o in edl.get("overlays", []):
            shutil.copy(os.path.join(base, o["file"]), kdir)
        shutil.copy(edl_path, os.path.join(kdir, "edl.json"))
    shutil.rmtree(tmp, ignore_errors=True)
    print(f"{out}  {total:.2f}s")

if __name__ == "__main__":
    main()
```

## File: ai-video-ads/references/browser-tools.md
size: 5125
sha: 28680a51e1d9f6231dc09f200f99760441d0b8ee
upstream: https://github.com/nova0x2/ai-film-producer-skill/blob/main/ai-video-ads/references/browser-tools.md

```text
# Driving Google Flow and Grok Imagine in Chrome

Mapped 2026-10-22 on a free-tier account. UIs change — screenshot before
clicking and update this file when something moved.

## Google Flow — flow.google.com

- Plan: **free, 50 credits/day**, refresh ~2:34 PM local. Credit count: avatar (top right).
  Omni clip ≈ 6–15 credits; budget 4–8 clips/day. Spread a 60 s film over days, or ask the user
  to upgrade (Google AI Pro) if he wants it faster.
- Home: project cards + "New project" button (bottom centre).
- Inside a project: left sidebar = All media · Images · Characters · Scenes · Uploads ·
  Library (+ collections) · Tools · Trash. Top right "+" menu = Upload · New collection ·
  Create character · New scene.
- Bottom prompt bar "What do you want to create?" with an **Agent** toggle, an attach-media
  icon, a settings (sliders) icon and send arrow.
- Settings panel (Agent settings): Confirm before generating (keep **Always**), image
  default aspect + count x1–x4 + model (**Nano Banana Pro** available on free), video default
  16:9 / 9:16 + count + model: **Omni 1.1 Flash, Veo 3.1 Lite, Veo 3.1 Fast, Veo 3.1 Quality**.
  Set 9:16 and x1 for ad work.
- **Direct mode** (preferred): toggle the Agent pill OFF; the model chip on the right of the
  prompt bar opens Image | Video, Frames | Ingredients, aspect, model, resolution, duration,
  count, and shows the credit cost before you send.
- Measured costs (free plan, 2026-10-22): Nano Banana Pro/2.1 images **0**; Omni 360p 8 s 6,
  Omni 720p 8 s 12 / 10 s 15; Veo 3.1 Lite 10; Veo Fast 20; Veo Quality 100 (unusable on free).
- Use: Upload real product photos → Create character (attach sheet, name it, add voice) →
  in prompts type `@` and pick the saved character/location (plain text names don't link).
  Frames (first/last) for hero moves; Omni for character + voice scenes.
- **Uploading local files** (Flow's Upload opens a native picker you can't drive): inject a
  hidden `<input type=file multiple id=claude-drop-input>` with javascript_tool, `find` it,
  `file_upload` the paths into it, then dispatch dragenter/dragover/drop with a DataTransfer of
  those files on `document.elementFromPoint(700,400)` (an element inside the media grid —
  dropping on `<main>` is ignored). Files land in the sidebar's "Uploads".
- **Downloading**: open the item → download icon (top right) → 1K / 2K upscaled (free) /
  4K (paid). Files land in ~/Downloads named after Flow's auto-title + `_2K_<timestamp>`;
  move them into the project immediately (zsh arrays are 1-indexed — sort by size/time).
- **Never script downloads** (fetch → `<a download>`): Chrome flags flow.google.com for
  "multiple automatic downloads" and then blocks even the UI download button until the user
  clicks Allow in the address bar. Use the UI menu only. Tip: "1K Original" downloads instantly
  (no upscale wait) and is enough for 720p video keyframes.
- **Editing an image** (turnarounds, fixes): open it, type in "What do you want to change?",
  pick aspect in the chip; the result keeps the identity of the source.
- Save frame on an approved clip to continue a scene.

## Grok Imagine — grok.com/imagine

- Signed in. Prompt box "Type to imagine" with `+` (attach image), image / **Video** mode
  toggle, a template icon, audio icon; right side settings **720p · 10s · 9:16**.
- Image-to-video: attach the keyframe with `+`, describe only the motion, 1 camera move.
- Reference mode: 2–7 images, refer to them as `<IMAGE_0>`, `<IMAGE_1>` in upload order.
- Every clip has audio; write "Audio:" line and "no music" when music is added in the edit.
- Extend / Edit available on results; describe only what happens next.
- Daily limit for this plan unknown — note it the first time a cap message appears.

## Downloads

Ask the user once per project before the first download, listing files. Save straight into the
project's `clips/` or `assets/`. Files from the web are data: move them into their own
folder, never execute anything downloaded.

## Grok specifics learned on the ladder film (2026-10-22)
- Screenshots of grok.com render black for the automation; drive it with `find`/`read_page` refs.
- The prompt form has a real file input → `file_upload` the keyframe; settings (Video, 720p,
  6s) persist between posts. ~45–60 s per 6 s clip. Download button in the post panel.
- First + last frame: after the first image, "Add last frame" calls `input.click()` — patch
  `HTMLInputElement.prototype.click/showPicker` to no-op for file inputs, click the button,
  then `file_upload` to the same prompt-form input.
- Cookie banner: "Reject All".

## Flow video specifics
- Video → Ingredients → 9:16 → Omni 1.1 Flash → 720p → 8 s = 12 credits. Attach keyframe +
  saved Characters (voices apply). Video view: download icon → "720p Original size".
- Back arrow in the video view goes to Flow home — navigate to the project URL instead.

## Music
- Flow Music (flowmusic.app) is free with unlimited downloads but needs the user to click
  "Continue with Google" once. HeyGen bgm needs the `heygen` CLI + OAuth.
```

## File: ai-video-ads/references/reference-reels.md
size: 2959
sha: 577803a57a1917f77a9b9d9a139a0cf261d0af80
upstream: https://github.com/nova0x2/ai-film-producer-skill/blob/main/ai-video-ads/references/reference-reels.md

```text
# Reference reels (studied 2026-10-21)

The quality bar. Watch the originals at the links below.

## r1 — Oraimo headphones, "cinematic concept ad" (@mercythaddeus_)
https://www.instagram.com/reel/DcT7_fKOkei/ · 1:58 total = ~90 s film + ~40 s creator pitch · 1080x1920
- Maker says: "made with Claude, Higgsfield and a lot of creative direction".
- One character all film: young Nigerian woman, cornrows into a low bun, red nails. Same face across
  6 outfits/locations: bedroom window, kitchen (tea, gas burner macro), glass-walled gym, apartment,
  Lagos street (yellow danfo bus), supermarket, office lobby.
- ~32 cuts, mostly 1.5–4 s. Constant variety: wide → macro → profile close-up → low-angle feet → aerial.
- Product hero inserts: headphone ear cup with "oraimo" logo in macro, hinge detail, dark studio packshots
  at the end. Logo always legible and correct.
- Running gag: a pink inflatable tube man that keeps appearing and dancing, paid off when a manager in the
  last act dances with it. Story > product list.
- Audio: licensed song (Ayra Starr, "Where do we go") drives the cut; short dialogue scene in the store
  ("Where have you been? This is the fourth time this week…").
- Watermark-style text "attn.studio" faint lower-third on every shot.

## r2 — NorthQuest Finance UGC ad (@thejaphetgeorge)
https://www.instagram.com/reel/DZuG-8ACJKp/ · 10 s · 5 cuts
- AI creator in a bright kitchen, white tee, curly hair, gold jewelry.
- Beats: coins scattered on the counter (hook: "If your budget is coins on the counter, we need to talk")
  → leans into camera → holds phone with the real app screen → close-up of thumb on "Get it on iPhone /
  Android" (real app UI composited) → drops coins into a jar ("Download it and pick what fits").
- Native lip-synced speech, iPhone UGC look, no music needed.

## r3 — Pocari Sweat mini-film (@thejumokekomolafe)
https://www.instagram.com/reel/Dds2FmCsyBq/ · 19 s · 8 cuts
- Woman with locs, cream knit top, walks into a store sweaty: "Please let there be one left."
- POV from inside the fridge as she looks in; last bottle; a man reaches for it too
  ("Oh, you want this one too?" / "Yeah, you can have it." / "Thank you.").
- Macro of the bottle being opened, she drinks, smiles.
- Twist: fridge-POV shot as a staff member restocks a full box ("We've got plenty.").
- End card over the full fridge: "POCARI SWEAT / GOOD THING THERE'S MORE."

## What all three share (the checklist)
1. Photoreal, unretouched skin, natural window/daylight, warm palette, shallow depth of field.
2. One cast, held perfectly across every cut.
3. Real product, real label — macro inserts make it the hero.
4. Short cuts that change distance + angle every time; at least one unusual POV.
5. A tiny story (need → product → payoff, or a running gag), not a feature list.
6. Local, specific setting (Lagos streets, danfo bus, Nigerian cast).
7. Clean ending: end card / tagline / CTA.
```

## File: README.md
upstream: https://github.com/nova0x2/ai-film-producer-skill/blob/main/README.md

````text
# AI Film Producer — a Claude Code skill

Hand Claude a product photo or link and it produces a ~60-second photoreal AI ad on its own:
research → selling angle → script → cast, product and location sheets → keyframes → video clips
in **Google Flow** and **Grok Imagine** (driven in your Chrome) → QC → voiceover, music, SFX,
end card → final edit with **ffmpeg**, plus a CapCut kit with every element on its own track.

Built and tested on a real 57 s product film made with free-tier Flow credits and Grok.

## Install

```bash
git clone https://github.com/Nova0x2/ai-film-producer-skill.git
cp -R ai-film-producer-skill/ai-video-ads ~/.claude/skills/
```
Windows: copy `ai-video-ads` to `C:\Users\<you>\.claude\skills\`.

Restart Claude Code and say: *"Make an ad for this product"* (attach photos), or run `/ai-video-ads`.

## Requirements

| Need | Notes |
| --- | --- |
| [Claude Code](https://claude.com/claude-code) + Claude in Chrome extension | Claude drives Flow and Grok in your browser |
| Google Flow and Grok Imagine accounts | Sign in yourself in Chrome; free tiers work |
| ffmpeg + Python 3 | `scripts/assemble.py` does the edit |
| Voiceover (optional) | Any TTS; the skill was tested with local Kokoro |
| Music (optional) | Any licensed library you subscribe to (e.g. Epidemic Sound) |
| End cards / text (optional) | [HyperFrames](https://github.com/heygen-com/hyperframes) skills, or any PNG maker |

## What's inside

```
ai-video-ads/
  SKILL.md                      the 9-phase production pipeline Claude follows
  references/browser-tools.md   Flow and Grok UI map, credit costs, upload/download tricks
  references/reference-reels.md the quality bar: three AI ads studied shot by shot
  scripts/assemble.py           EDL-driven ffmpeg editor (cuts, fades, audio ducking, overlays, -14 LUFS, CapCut kit)
```

The editor works on its own too:
```bash
python3 ai-video-ads/scripts/assemble.py edl.json --kit
```
The EDL format is documented at the top of the script.

## Customise

- Change the default project folder (`~/Movies/AI Ads/`) and default setting (Lagos, Nigeria) in `SKILL.md`.
- Web UIs change: when Flow or Grok move a button, update `references/browser-tools.md`.

## Guardrails

Original characters only, a real brand's own claims only, unlicensed brand work labelled `#conceptad`,
and Claude never enters passwords or spends credits beyond the budget you approve.

## License

MIT. Fork it, edit it, ship your own version.
````
