/bw-song-ad — Complete Black-and-White Cartoon Song Ad
2026-10-18, created with the procedure from H-OHI-023a (Ohisama Pants). Reference: X's WhiteHat MRR (bone density, black-and-white cartoon, ~4 minutes). The response was "the song is fire, it's godly." The reference video is not a short jingle but a long-form VSL script sung almost verbatim.
Order (do not change)
Lyrics → Song (Suno) → Character-level timing → Scene division → Character sheet → Keyframes → Animation → Background export → Remotion → Quality check. The song is the clock. Creating visuals first will cause rework because they won't align with the song.
S=~/.agents/skills/bw-song-ad
AD=<project folder>/<ad ID> # lyrics.md / captions.txt / song/ / shots.py / keys/ / clips/ / remotion/
cp -R $S/remotion $AD/remotion && cd $AD/remotion && NODE_USE_ENV_PROXY=1 npm install
0. Check Existing Assets First
If there are previously created images/videos for the same brand, use them first (check the brand asset inventory table ASSETS.md; update it when new ones are made). When generation is needed, report upfront: "Approximately X images, Y videos, ~Z credits." This run: 61 images (~15 credits), 61 Grok videos (~265 credits).
1. Lyrics (lyrics.md) — Template in reference/lyrics.md
- Sing the VSL flow in paragraphs. Do not rhyme. Structure mirrors the reference 1:1: Expert opening → "it's not that" ×3 → Something more important → Cause is not where you think → Internal allies vs. villains → Used to work → Now villains are winning → The problem chain → Store has the same stuff → But things changed (solution) → Article (no registration needed) → Inventory → "No need to..." ×3 → "It's okay. Tap below."
- All in hiragana. Numbers also in hiragana (Suno breaks on kanji and digits).
- Do not sing product names (display via on-screen labels). No prices. Promises stay within the product page scope.
- Show before composing.
2. Song (Suno / Playwright)
- Only login is manual. Then inject lyrics, Style, Title into
suno.com/create?mode=CUSTOMand press Create. - Style (best):
1930s vintage upbeat jazz, old cartoon soundtrack, swing, clarinet, muted trumpet, ragtime piano, brushed snare, upright bass, warm female vocal, Japanese, clear diction, storytelling sung melody - Generates 2 tracks. Transcribe both with whisper and compare to lyrics; pick the one with fewer errors.
- Download: track page → More options → Download → MP3 → "Unlock & Download" (uses one free credit).
cdn1.suno.aireturns 403; encrypted playback m4a. Do not extract browser blob to bypass limits. - Place at
song/song.mp3.
3. Caption Text (captions.txt)
- One line per lyrics paragraph. Split lines with
/(one segment = one subtitle on screen, ~12 characters max). - Convert to readable mixed kanji/kana Japanese (lyrics are hiragana; captions use standard notation).
- For two-line segments, use
|(e.g.,Tap the button below|to read the article).
4. Character-Level Timing
uv run --python 3.12 --with openai-whisper python $S/scripts/align.py $AD
→ Produces $AD/cap_events.json (segment start/end times) and $AD/line_starts.json (each line's start).
- Do not use whisper-cli's line-level timing. It treats the intro (silent first seconds) as starting at 0 seconds and distributes timing evenly within lines, causing subtitles to advance during silence.
- Hide subtitles during pauses longer than 0.8 seconds.
- If alignment accuracy <80%, the song or captions.txt is misaligned.
5. Scene Division (shots.py)
- Define
STYLE(common visual description for all cuts) andSHOTS = [(line number, cut index within line, scene description, motion), ...]. - Each cut: 2–5 seconds. Long lines split into 2–3 cuts. ~60 cuts for 3.5 minutes.
- Do not animate all long shots: mix static images with slow zooms to halve video generation needs.
- Characters: Teacher (Bob, apron) / Mom (low bun, stripes) / Child (bob cut) / Two internal messengers (striped overalls, hats) / Villain (black sphere, horns, sponge & straw) / Product (actual pattern).
6. Character Sheet & Keyframes (Higgsfield gpt_image_2_5 / 9:16 / 0.25 credits each)
- First, create one character sheet image. For all subsequent cuts, pass both the character sheet + one reference frame as
image_references. - Prepend
STYLE: black and white 1930s rubber-hose cartoon, thick clean ink lines, cross-hatching, grayscale only, vintage Fleischer-era look, slightly grainy film, soft dark round vignette, no text. - Run:
python3 $S/scripts/make_reqs.py $AD image→ batch 12 at a time withgenerate_image_batch. - Dress the child (underwear/diaper view triggers NSFW rejection). One reference frame showing underwear alone can cause rejection.
- Write the actual product pattern in the prompt (re-create if different pattern appears).
- Review all frames via contact sheet: check style consistency, no text, character sheet conformity.
7. Video Generation (Grok Imagine grok_video_v15_lite / 720p / start_image only / ~1 credit per second)
- Run:
python3 $S/scripts/make_reqs.py $AD video→ batch 12 at a time withgenerate_video_batch. Duration = segment length + 0.5s (3–8s total). - Prompt: "Animate this image: . Keep the same style, composition, and characters; bouncy rubber-hose motion, film flicker, fixed camera, no cuts, no new text."
- If rejected and a preset is returned, re-submit with
declined_preset_idin params. - Review all final frames side by side. If the product turns sideways and its pattern breaks, use only frames before the break (
SRC_MAX) or loop the front section.
8. Export Background (No Text)
python3 $S/scripts/bg.py $AD # → $AD/bg.mp4 (1080x1920 / 30fps) and shot_starts.json
- Cut transitions align with line start times (line_starts.json). First cut starts at 0s.
- To fill an instrumental intro, consider fast cuts of strong scenes (villain, flood, stamping, etc.).
9. Remotion (at $AD/remotion)
- Place
public/bg.mp4,public/song.mp3,src/data.json:python3 $S/scripts/make_data.py $AD --hook-end 29.3 --cta 192.9 --brand 44,46,47,50 --hook-top "For moms potty training" --hook-main "Diapers won't come off" --hook-mark "The real reason" --brand-name "<product name>" --keys "<key phrases in yellow, comma-separated>" - Components (
src/SongAd.tsx):- Top hook: Paper card (thick black border, offset shadow, slight rotation) showing "who it's for + what it's about" in the first second. Example: "For moms potty training / Diapers won't come off The real reason".
- Subtitles: Black rounded rectangle, white bold text, pops in with bounce. Key phrases only in yellow (the sole color in black-and-white).
- Product label: Small text above product-only cuts.
- CTA: From the final "Tap below" to the end, three yellow arrows bounce down + "Tap the button below|to read the article".
- Export (this Mac's headless Chrome crashes standalone → use
chrome-wrap.sh, outside sandbox):
cd $AD/remotion && NODE_USE_ENV_PROXY=1 npx remotion render src/index.ts SongAd out/<ad ID>.mp4 --browser-executable=./chrome-wrap.sh --concurrency=6 --crf=20
- Delivery: re-encode with
-crf 23 -movflags +faststart(heavy due to grain; ~200MB for 3.5 min).
10. Quality Check (mandatory before opening)
- Read a contact sheet of ~24 frames (one per second): check subtitles align with song / not appearing during silence / product pattern intact / CTA arrow stays on screen / no stray text.
- Check first 3 seconds separately (hook immediately readable).
- Create an HTML preview page and open it. Use the AdLab issued name (H-XXX-000a) as the filename.
Pain Points (measured this run)
- Subtitles drift from start: song starts at 4.9s but whisper-cli assumed 0s → solved by
align.py(openai-whisper word_timestamps + difflib). - NSFW keyframes: child in underwear / reference frame with underwear → dress the child / describe only the style in the reference.
- Product rotates and breaks pattern: Grok often turns sideways. Use only front-facing seconds.
- Over-production: 61 images + 61 videos for one ad. Next time: reuse inventory and static images with zooms to halve.
- Last character of a long line drops to next line → specify line breaks with
|.
Related: reference/lyrics.md (lyrics template and examples), /zackd-ad (3D animation type), AdLab (register hypothesis before creation).