Review finished ad skill
Final QC gate for a rendered 9:16 video ad, run before it is published.
by gooseworks-ai·MIT license·★ 1,220 Stars on the repo·GitHub ↗
npx degit gooseworks-ai/goose-skills/skills/ads/capabilities/review-finished-ad#main ~/.claude/skills/review-finished-adChecked ·commit main
Files of Review finished ad
Show the full text99 lines
review-finished-ad
The gate between a rendered master and the user. A finished ad has to look like the brand's ad and play like an ad, not like a pile of clips. This skill checks both, fixes are made by the caller, and the gate is re-run until it passes.
Run it
Needs ffmpeg + ffprobe on PATH and pip install --quiet numpy pillow.
python3 scripts/review_finished_ad.py \
--video working/final.mp4 \
--json working/review/finished-ad.json \
--sheet working/review/finished-ad-sheet.png \
--logo working/brand/logo.png \
--palette "#0b3d2e,#f4efe6" \
--product-images working/brand/product-1.png,working/brand/product-2.png \
--font working/brand/font.ttf --brand-name "Acme"
--logois the logo file the video actually composites: the kit'slogoUrl/logos[0], or the wordmark file the recipe's end card uses. PNG, JPEG or SVG (an SVG is rasterised withcairosvgorrsvg-convert). Never a generated logo.- No logo image in the video (the brand name set as text in the brand font, because the
kit only has a favicon or no logo): omit
--logo, and judge the text wordmark on the sheet. --endcard-sis the end card's length (default 3.0). Set it to the real length: a longer silent or static end card would otherwise read as dead air or a freeze.--logo-at 3.2(repeatable) adds a timestamp where the logo also appears mid-video; the end card (last ~1.6s) is always checked.--no-speechfor formats with no voiceover or dialogue (music-only), so silence is not judged.--expect-sizedefaults to1080x1920. Video ads are always 9:16.
Exit 0 → PASS. Still read the sheet (below) before publishing.
Exit 2 → FAIL. failed lists the checks; each note says what to fix.
Exit 3 → ERROR. The check could not run (missing file, no ffmpeg); fix and
re-run. Never publish blind.
Machine checks
| Check | Fails when | Typical fix |
|---|---|---|
ratio |
output is not exactly 1080x1920 | scale + pad every clip to 1080x1920 BEFORE the concat |
hook |
no sound in the first 1.0s, or the opening frame is still for > 1.5s | start the VO/music at 0s; open on motion or a cut, not a held title |
pacing |
the picture is frozen for > 2.5s before the end card | add motion (push-in, b-roll, a cut) or trim the hold |
dead_air |
silence > 1.0s mid-video (skipped with --no-speech) |
tighten the VO timing or run the music bed under the gap |
black_frames |
a black stretch > 0.3s | fix the concat / transition |
logo_asset |
the logo file is favicon-sized (long side < 256px, or under 40,000 px²) | do not upscale it: ask for a real logo, or set the wordmark as text in the brand font (then drop --logo) |
logo |
the kit logo is not found on the end card (below the fail line for its mode) | composite the uploaded logo file onto the end card; never regenerate or retype it |
palette |
(warn only) no kit colour among the end card's main colours | use a kit colour for the end-card background or text |
logo is grayscale correlation with a fine size search, so the right logo scores
0.9+ at any size:
- Transparent mark (PNG/SVG wordmark or symbol): matched in either polarity, so a white version on a dark card counts. Pass ≥ 0.85, fail < 0.75. Another brand's wordmark scores about 0.6-0.7; a same-font near-copy about 0.8 (warn).
- Opaque logo (a JPEG, a mascot photo, a square app icon): matched as the whole image. Pass ≥ 0.85, fail < 0.70. A different mascot on a similar background scores about 0.5.
Between the two lines it is a warn: look at the sheet for a warped, cropped or redrawn
logo.
Eye checks — read the sheet every time
finished-ad-sheet.png shows one frame per shot plus the end card, with the
platform UI areas shaded red, and the brand references underneath. Open it and
confirm each line in the verdict's judge_on_sheet:
- Safe zones: no caption, CTA, price, logo or product name inside a red band (top 220px, bottom 400px, right 140px at 1080x1920).
- Font: on-screen text uses the brand font shown in the specimen.
- Product likeness: every product shot matches the reference product images (shape, label, colour). A stand-in, a catalogue image of another product, or a mascot is a fail.
- Product consistency: the product looks the same in every scene.
- Logo unaltered: not stretched, recoloured, cropped or redrawn.
Any of these failing is a FAIL, the same as a machine check.
Fix loop
Fix only the failing window (re-composite the end card, regenerate one shot, re-time one line), re-render, and re-run this script. Up to 2 repair rounds. If it still fails, do not present the video as finished: the caller reports it as blocked with the failed checks, so the user sees a clear warning.
Output
--json writes { verdict, failed[], video{width,height,duration,has_audio,cuts}, checks{<name>: {status, note, data}}, sheet, judge_on_sheet[] }. Status is one of
pass, fail, warn, not_applicable.
| 1 | |
| 2 | name review-finished-ad |
| 3 | description Final QC gate for a rendered 9:16 video ad, run before it is published. One script checks what a machine can decide — exact 1080x1920 size, a hook that moves and speaks in the first second, no frozen stretches, no dead air, no black frames, the brand's real logo on the end card (never a favicon, never a redrawn or wrong logo), end-card colours near the brand palette — and builds one contact sheet (frames with the TikTok/Reels UI safe zones shaded, beside the logo, product images and a font specimen) for the checks that need eyes — font, product likeness, product consistency across scenes, safe zones. Exit 0 PASS / 2 FAIL / 3 ERROR. Use on every finished video master before pinning it. |
| 4 | |
| 5 | |
| 6 | # review-finished-ad |
| 7 | |
| 8 | The gate between a rendered master and the user. A finished ad has to look like |
| 9 | the brand's ad and play like an ad, not like a pile of clips. This skill checks |
| 10 | both, fixes are made by the caller, and the gate is re-run until it passes. |
| 11 | |
| 12 | ## Run it |
| 13 | |
| 14 | Needs `ffmpeg` + `ffprobe` on PATH and `pip install --quiet numpy pillow`. |
| 15 | |
| 16 | |
| 17 | python3 scripts/review_finished_ad.py \ |
| 18 | --video working/final.mp4 \ |
| 19 | --json working/review/finished-ad.json \ |
| 20 | --sheet working/review/finished-ad-sheet.png \ |
| 21 | --logo working/brand/logo.png \ |
| 22 | --palette "#0b3d2e,#f4efe6" \ |
| 23 | --product-images working/brand/product-1.png,working/brand/product-2.png \ |
| 24 | --font working/brand/font.ttf --brand-name "Acme" |
| 25 | |
| 26 | |
| 27 | `--logo` is the logo **file the video actually composites**: the kit's `logoUrl` / |
| 28 | `logos[0]`, or the wordmark file the recipe's end card uses. PNG, JPEG or SVG (an SVG is |
| 29 | rasterised with `cairosvg` or `rsvg-convert`). Never a generated logo. |
| 30 | **No logo image in the video** (the brand name set as text in the brand font, because the |
| 31 | kit only has a favicon or no logo): omit `--logo`, and judge the text wordmark on the sheet. |
| 32 | `--endcard-s` is the end card's length (default 3.0). Set it to the real length: a longer |
| 33 | silent or static end card would otherwise read as dead air or a freeze. |
| 34 | `--logo-at 3.2` (repeatable) adds a timestamp where the logo also appears |
| 35 | mid-video; the end card (last ~1.6s) is always checked. |
| 36 | `--no-speech` for formats with no voiceover or dialogue (music-only), so |
| 37 | silence is not judged. |
| 38 | `--expect-size` defaults to `1080x1920`. Video ads are always 9:16. |
| 39 | |
| 40 | **Exit 0 → PASS.** Still read the sheet (below) before publishing. |
| 41 | **Exit 2 → FAIL.** `failed` lists the checks; each `note` says what to fix. |
| 42 | **Exit 3 → ERROR.** The check could not run (missing file, no ffmpeg); fix and |
| 43 | re-run. Never publish blind. |
| 44 | |
| 45 | ## Machine checks |
| 46 | |
| 47 | | Check | Fails when | Typical fix | |
| 48 | |---|---|---| |
| 49 | | `ratio` | output is not exactly 1080x1920 | scale + pad every clip to 1080x1920 BEFORE the concat | |
| 50 | | `hook` | no sound in the first 1.0s, or the opening frame is still for > 1.5s | start the VO/music at 0s; open on motion or a cut, not a held title | |
| 51 | | `pacing` | the picture is frozen for > 2.5s before the end card | add motion (push-in, b-roll, a cut) or trim the hold | |
| 52 | | `dead_air` | silence > 1.0s mid-video (skipped with `--no-speech`) | tighten the VO timing or run the music bed under the gap | |
| 53 | | `black_frames` | a black stretch > 0.3s | fix the concat / transition | |
| 54 | | `logo_asset` | the logo file is favicon-sized (long side < 256px, or under 40,000 px²) | do not upscale it: ask for a real logo, or set the wordmark as text in the brand font (then drop `--logo`) | |
| 55 | | `logo` | the kit logo is not found on the end card (below the fail line for its mode) | composite the uploaded logo file onto the end card; never regenerate or retype it | |
| 56 | | `palette` | *(warn only)* no kit colour among the end card's main colours | use a kit colour for the end-card background or text | |
| 57 | |
| 58 | `logo` is grayscale correlation with a fine size search, so the right logo scores |
| 59 | 0.9+ at any size: |
| 60 | |
| 61 | **Transparent mark** (PNG/SVG wordmark or symbol): matched in either polarity, so a white |
| 62 | version on a dark card counts. Pass ≥ 0.85, fail < 0.75. Another brand's wordmark scores |
| 63 | about 0.6-0.7; a same-font near-copy about 0.8 (warn). |
| 64 | **Opaque logo** (a JPEG, a mascot photo, a square app icon): matched as the whole image. |
| 65 | Pass ≥ 0.85, fail < 0.70. A different mascot on a similar background scores about 0.5. |
| 66 | |
| 67 | Between the two lines it is a `warn`: look at the sheet for a warped, cropped or redrawn |
| 68 | logo. |
| 69 | |
| 70 | ## Eye checks — read the sheet every time |
| 71 | |
| 72 | `finished-ad-sheet.png` shows one frame per shot plus the end card, with the |
| 73 | platform UI areas shaded red, and the brand references underneath. Open it and |
| 74 | confirm each line in the verdict's `judge_on_sheet`: |
| 75 | |
| 76 | **Safe zones:** no caption, CTA, price, logo or product name inside a red band |
| 77 | (top 220px, bottom 400px, right 140px at 1080x1920). |
| 78 | **Font:** on-screen text uses the brand font shown in the specimen. |
| 79 | **Product likeness:** every product shot matches the reference product images |
| 80 | (shape, label, colour). A stand-in, a catalogue image of another product, or a |
| 81 | mascot is a fail. |
| 82 | **Product consistency:** the product looks the same in every scene. |
| 83 | **Logo unaltered:** not stretched, recoloured, cropped or redrawn. |
| 84 | |
| 85 | Any of these failing is a FAIL, the same as a machine check. |
| 86 | |
| 87 | ## Fix loop |
| 88 | |
| 89 | Fix only the failing window (re-composite the end card, regenerate one shot, |
| 90 | re-time one line), re-render, and re-run this script. Up to **2 repair rounds**. |
| 91 | If it still fails, do not present the video as finished: the caller reports it as |
| 92 | blocked with the failed checks, so the user sees a clear warning. |
| 93 | |
| 94 | ## Output |
| 95 | |
| 96 | `--json` writes `{ verdict, failed[], video{width,height,duration,has_audio,cuts}, |
| 97 | checks{<name>: {status, note, data}}, sheet, judge_on_sheet[] }`. Status is one of |
| 98 | `pass`, `fail`, `warn`, `not_applicable`. |
| 99 |
Discussion
Browse more free Claude skills.