render-cartoon-music-video
Assemble a cartoon / animated / hand-crafted music-video ad from a config — a sung song carries the whole narrative while N per-bar i2v clips (one recurring animated character, one look pack) are each cut to their BAR window from librosa beat-tracking and hard-concatenated on the
Install
npx skills add https://github.com/gooseworks-ai/goose-skills/tree/main/skills/ads/capabilities/render-cartoon-music-video
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install gooseworks-ai-goose-skills@llmmart
git clone https://github.com/gooseworks-ai/goose-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole gooseworks-ai/goose-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
render-cartoon-music-video
Assemble a cartoon music-video ad from a config: an animated / illustrated / hand-crafted story (stop-motion felt-and-foam, claymation, 2D toon, cut-out) where a sung song is the entire script and one recurring animated character carries every shot, cut to the bar with the climax line on the drop. This capability is the FREE, deterministic assembly — cut-to-bar, hard-concat, the logo bug + captions burn, and the solid-color end card.
scripts/config.example.json is the worked example (Coinbase "Bet on anything", ~55s 1080×1920
9:16, 16 body bars + a 3s end card); scripts/PIPELINE.md maps every config block to its source
step and scripts/README.md documents the free assembly.
Run
This is the FREE, deterministic assembly stage — it spends nothing. The paid inputs are
separate capabilities: the sung song (create-music-elevenlabs, or a user-supplied mp3 + Whisper
word timings) beat-tracked with librosa so the BAR GRID sets the timeline; one locked recurring
character + one keyframe per bar in one look pack (create-image-fal, Nano Banana); and one
Seedance i2v clip per bar (create-video-fal). Given the song + word-timestamps.json +
bars.json + one clip per bar + the brand wordmark SVG, render-cartoon-music-video cuts each
clip to its bar window, hard-concats on the bar, burns the logo bug + captions, appends the
solid-color end card, and muxes the song under it → the master. Re-cuts reuse the existing song /
keyframes / clips and cost $0.
Contract (the free assembly)
- The sung song carries the narrative — no separate VO. The generated/supplied track IS the bed (no VO to duck under); do not add a spoken voiceover or a second bed.
- Plan the timeline AROUND the delivered song's BAR GRID. Beat-track the song with librosa (assume 4/4); one bar = one shot (default). Snap every tableau window to the bar boundaries — never trim the song to a pre-planned grid.
- Captions from the song's word timings, re-spelled against the locked lyrics. Whisper
mishears shouted accents ("BETS" → "Hearts"); re-spell the timed tokens against the locked
lyric file (never edit lyrics to match Whisper). VEED-whisper white bold sans in the
BOTTOM third (Alignment 2,
margin_vabove the bottom-left logo bug), ~4–5 words per cue, NO background pill; captions STOP at the end-card boundary. Never mid-frame — it covers the character (fixed 2026-07). If the host ffmpeg lacks libass, render the cues as timed PIL PNG overlays (ffmpegoverlay=…:enable='between(t,st,en)') at the same bottom placement. - Land the climax line on the drop. The climax tableau is timed so the payoff line sits on the sub-bass drop; accent that line.
- Persistent logo bug, suppressed on the end card. Burn a white brand wordmark bottom-left over the body bars (cairosvg → PIL from the real SVG), suppressed on the end card where the big wordmark dominates.
- End card via cairosvg + PIL from the real wordmark — never AI-render brand text. Solid brand-color card + white wordmark + subhead, holding ~3s WITH the song still playing under it (afade-out over the tail — no silent tail). A diffusion model garbles a wordmark.
- FFmpeg composite, deterministic, FREE. Cut each clip to its bar window, hard-concat, burn
the logo bug + captions, append the end card, mux the song over the whole video with a 0.5s
afade tail,
loudnorm I=-14→ a 1080×1920 h264+aac master. No paid calls, no keys. When the host ffmpeg lacks libass/drawtext, BOTH the captions and the logo bug are timed PIL PNG overlays (overlay=x:y:enable='between(t,st,en)'), not an ASS burn. - QC PER SCENE, never just the master — the two failure modes are content, not assembly. The upstream keyframe/i2v steps can (a) drift the character felt → smooth-3D "man" partway through and (b) hallucinate hands — realistic fingers in a hand macro, a pointing finger on a "tap the phone" shot, or a disembodied hand sliding in from the frame edge. Both hide at thumbnail size. Extract a 2 fps contact sheet + a per-bar FACE crop and HAND crop (across each clip's full duration), confirm every bar is matte felt with the ONE character and no human/floating hands, and re-check the served bytes after publish. A drifted bar means regenerating that bar's KEYFRAME (not re-cutting) — see the recipe STEP 3/6.
Files (goose-skills)
-
scripts
-
config.example.json 21.1 KB
{ "_comment": "Coinbase 'Bet on anything' cartoon music-video — THE WORKED EXAMPLE. Copy to config.json and edit. A ~55s felt-and-foam-core stop-motion 9:16 music video where a SUNG song carries the whole narrative (NO separate VO) and ONE recurring felt character walks miniature sportsbook dioramas. Pipeline: (1) lock the song (here user-supplied) + Groq Whisper word timings + librosa bars.json (the BAR GRID sets the timeline, one bar = one shot); (2) lock the felt character (nano_banana_2 anchor + 4 drift-checked angles); (3) one nano_banana_2 keyframe per bar (9:16, ONE look_pack, anchor ref threaded into character shots); (4) one seedance_2_0 i2v clip per bar (veo3_1_lite fallback on ip_detected/nsfw), CHARACTER-VERB-led motion; (5) captions from the word timings re-spelled against the locked lyrics, persistent logo bug, hard-cut assembly + solid-Coinbase-Blue PIL end card + song-under-end-card. Master 1080x1920 h264+aac, ~54.9s (16 body bars + 3.0s end card). See scripts/PIPELINE.md for the source scripts.", "brand_name": "Coinbase", "campaign": "Bet on anything", "title": "Prediction markets — now on Coinbase", "width": 1080, "height": 1920, "fps": 30, "duration_sec": 54.9, "aspect_ratio": "9:16", "song": { "_comment": "Locked FIRST — the song + its BAR GRID set the timeline (one bar = one shot). Here `source` = user_supplied: the mp3 landed before State 2.6 could fire ElevenLabs, so word timings came from Groq whisper-large-v3 word-level, not native music_v1 timestamps. `source` = generate would call ElevenLabs music_v1 from `structure` + `prompt`. STRIP `[...]` section markers from the lyric text before ANY music-gen call — the brainstorm lyrics still had `[CTA — 2 bars]` and the model SANG 'CTA, two bars' over bar 22 (unusable). No artist names in `prompt` (ElevenLabs ToS filter).", "source": "user_supplied", "provider": "elevenlabs", "model": "music_v1", "lyrics_md": "lyrics/lyrics-locked.md", "lyrics_plain": "lyrics/lyrics-plain.md", "_note_lyrics_plain": "Feed sync-captions the lyric TEXT only (no bar-grid table / no headers). Feeding the full lyrics-locked.md parsed 700+ 'words' from markdown table rows and near-zero alignment.", "bpm": 73.83, "_note_bpm": "Brief targeted 124 BPM; delivered track is 73.83 BPM half-time (143.55 BPM dynamic — classic half-time-trap beat-track ambiguity). ~3.25s/bar gives a comfortable ~1 shot / 3.2s music-video rate. NOT a regression — the song sets the tempo, not the plan.", "time_signature": "4/4", "bar_duration_s": 3.25, "prompt": "Hype, brash dance-pop / EDM-trap hybrid with jersey-club horn stabs. Male lead, half-shouted half-rapped, hyperpop pitched-up backing vocals on the chorus. Four-on-the-floor kick, trap snare/hat pattern, brass-stab hook, sub-bass on the drop. Verse: driving, sports-broadcast energy. Pre-drop: build. Chorus: full drop, shouted accent hits ('BETS', 'WIN'). Bridge: climax line lands on the sub-bass drop. Brooklyn-rooftop-watching-the-playoffs energy.", "force_instrumental": false, "hook_line": "BETS — you called it", "climax_line": "Whoever wins — you called it", "climax_bar": 15, "_note_climax": "Chorus B ('Whoever wins — you called it') is the payoff. It repeats bars 15–17; the accent word CALLED lands on the drop. The song's attempt at 'Coinbase' (bar 18) is mumbled 'Can't base' and in the unusable region — the brand name is delivered on the END CARD, not the song.", "recommended_trim_end_s": 51.9, "recommended_trim_end_bar": 17, "_note_trim": "Full track is 78.24s; bars 18–24 are a degraded outro (mumbled 'Coinbase' + the 'CTA, two bars' artifact). Trim at 51.9s (end of the 3rd 'Whoever wins' — the strongest chorus repetition). Body video ends at bar 16 (51.9s); bar 17 window is the end card.", "structure": [ { "section": "Verse", "bars": "1-4", "lines": [ "Super Bowl Sunday, you knew the line", "Election night Tuesday, you saw it in time", "Oscars, the playoffs, the rate cut, the chain", "You called every shot — and got nothing to gain" ] }, { "section": "Pre-drop", "bars": "5-8", "lines": [ "They said the floor was closed", "Said the smart money already chose", "But the smart money's the money in your hand", "And tonight the floor is wherever you stand" ] }, { "section": "Chorus A", "bars": "10-13", "lines": [ "BETS — you called it", "BETS — you called it", "BETS — you called it", "BETS on the Knicks, on the chair, on the spread" ] }, { "section": "Bridge", "bars": "14", "lines": [ "WIN on the call you already said" ] }, { "section": "Chorus B (climax)", "bars": "15-17", "lines": [ "Whoever wins — you called it", "Whoever wins — you called it", "Whoever wins — you called it" ] } ], "outputs": { "audio": "audio/coinbase-prediction-market.mp3", "word_timings": "audio/word-timestamps.json", "beats": "audio/beats.json", "bars": "audio/bars.json" } }, "character": { "_comment": "ONE recurring ANIMATED character, locked once (LOCKED.json). `descriptor` threads VERBATIM into every character-bearing keyframe; the approved anchor job is passed as the media ref so the figure holds across bars. Skip the ref on style-only shots (S03, S05, S06). Anchor drift here was < 5% (method anchor-ref); drift > 15% would escalate to Higgsfield Soul ID (~$15).", "name": "felt_protagonist_1", "method": "anchor-ref", "max_drift": 0.05, "appears_in": "13 of 16 body shots (>60%)", "descriptor": "Single felt figure, gender-neutral build, age ~25. Coinbase Blue (#0052FF) sleeveless basketball-style felt jersey with a white felt stitched '1' on the front center. Medium-brown felt skin, short cropped tightly-coiled black-brown yarn hair, denim-blue felt shorts, white felt low-top sneakers. Holds a tiny felt Coinbase Blue phone with an embroidered white 'C' arc. Calm posture, never frantic.", "anchor": "generated/character-lock/anchor-portrait.png", "anchor_job": "bb01e794-a64c-403d-ab21-55754d34d8f2", "angles": { "3q-left": "generated/character-lock/angle-3q-left.png", "3q-right": "generated/character-lock/angle-3q-right.png", "action-medium": "generated/character-lock/angle-phone-up.png", "climax-hero": "generated/character-lock/angle-climax-confetti.png" }, "_note_angles": "The climax-confetti angle doubles as the S15 (climax) keyframe." }, "look_pack": { "name": "DIORAMA_FELT", "palette_anchors": [ "warm felt earth-tones", "foam-core cream/white", "Coinbase Blue #0052FF (jersey, phone, climax confetti only)", "soft practical warm key light" ], "style_descriptor": "Stop-motion miniature diorama, felt and foam-core construction, hand-built scale, macro photography, soft key + practical lighting, 1080x1920 vertical, slight tilt-shift focus. Felt is the dominant texture for soft elements (people, clothing, crowd); foam-core for hard elements (arenas, booths, stages). Hand-articulated stop-motion craft feel preserved.", "negative_tail": "NO photoreal humans, NO photorealism, NO smooth digital 3D render, NO AI-illustration look, NO oil-painting look. NO RULERS, NO PENCILS, NO MEASUREMENT TOOLS, NO HANDS (except the character's own felt hands), NO HUMAN-SCALE REFERENCE OBJECTS. NO readable text or brand logos in the world (brand appears only on the end card). Vertical 9:16 composition.", "_note": "ONE look pack drives all N keyframes so the sequence reads as one film. The 'no human-scale reference objects' negative is load-bearing: without it the model reads 'miniature' by ADDING a ruler/pencil/hand as a scale prop (S03, S05 got a literal blue pencil / an intruding hand)." }, "keyframe_engine": { "provider": "higgsfield", "model": "nano_banana_2", "aspect_ratio": "9:16", "batch_size": 3, "character_ref": "thread the locked anchor job (bb01e794-...) as media[role=image] on every character-bearing shot; for room continuity pass BOTH the anchor AND the prior keyframe (e.g. S04 off S02)", "_note": "nano_banana_2 honors multiple media refs. Never include readable brand text in a keyframe prompt — the model garbles it into typos." }, "clip_engine": { "provider": "higgsfield", "model": "seedance_2_0", "fallback": "veo3_1_lite", "fallback_triggers": ["ip_detected", "nsfw"], "duration_sec": 4, "batch_size": 3, "motion_opener": "CHARACTER-VERB LEAD (never camera-led): the character does a specific action, THEN any secondary background motion. Hand-articulated stop-motion craft feel preserved. NO camera 'slow push-in' opener (reads static), NO morphing, NO human-scale reference objects. ", "_note": "The single biggest quality lever (v1 -> v4): lead the prompt with the CHARACTER VERB, not the camera. 'Slow push-in toward the felt protagonist...' produced static clips; 'scrolls the phone with a thumb, glances up at the scoreboard...' read as music-video-grade animation. Auto-retry on veo3_1_lite when Seedance returns ip_detected (Oscar-trophy shapes) or nsfw ('intimate'/'leans toward' false positives)." }, "captions": { "_comment": "VEED-whisper BOTTOM captions (matches the ugc-walk-and-talk recipe: add-captions-veed-fal --preset whisper --position bottom --font-color #FFFFFF): clean white BOLD SANS in the lower third (ASS Alignment 2 + margin_v so it clears the bottom-left logo bug), NO pill, ~4-5 words per cue, >=0.9s on screen. Built from the song's word timings RE-SPELLED against the locked lyrics via align_to_lyrics — Whisper mishears shouted accents ('BETS' -> 'Hearts', 'WIN' -> 'Went'). Captions STOP at the end-card boundary (BODY_END_S = 51.9) so the brand frame breathes. Repeated hook phrases ('you called it' x3) had a Whisper caption gap — hand-patch the chorus repetitions. DO NOT use Alignment 5 (mid-frame) — it covers the character; bottom placement fixed 2026-07.", "source": "audio/word-timestamps.json", "chunks": "edits/chunks.json", "style": "whisper_bottom_sans", "position": "bottom", "font": "Arial Bold", "font_fallback": ["/System/Library/Fonts/Supplemental/Arial Bold.ttf", "/System/Library/Fonts/Helvetica.ttc"], "font_size": 76, "accent_font_size": 88, "accent_style": "bold", "no_pill": true, "alignment": 2, "margin_v": 180, "min_on_screen_s": 0.9, "outline_px": 6, "base_color": "#FFFFFF", "chunk_words": 4, "_note_chunk": "~4-5 words per cue (VEED whisper preset); >=0.9s minimum on screen, cue breaks on sentence-final punctuation.", "accent_words": ["BET", "BETS", "WIN", "WINS", "CALLED", "Whoever", "Knicks", "Coinbase", "opened", "bought"], "stop_at_end_card": true, "caption_end_s": 51.9 }, "tableaux": [ { "id": "S01", "bar": 1, "role": "VERSE", "t_start": 0.0, "t_end": 3.25, "uses_character": true, "lyric_anchor": "Super Bowl Sunday, you knew the line", "caption": "Super Bowl Sunday / you knew the line", "keyframe_prompt": "Miniature felt-and-foam-core stadium under tiny stadium lights, the felt protagonist on a front-row felt bench reading from a tiny felt Coinbase-Blue phone, jersey '1' visible. Character occupies ~60% of the vertical frame.", "motion_hint": "The protagonist scrolls the felt phone with a thumb and glances up at the scoreboard." }, { "id": "S02", "bar": 2, "role": "VERSE", "t_start": 3.25, "t_end": 6.5, "uses_character": true, "lyric_anchor": "Election night Tuesday, you saw it in time", "caption": "Election night Tuesday / you saw it in time", "keyframe_prompt": "Felt protagonist in a felt living room watching a tiny felt CRT TV showing foam-core squiggle probability graphs. Warm felt lamp practical.", "motion_hint": "The protagonist leans forward toward the TV, eyes widen." }, { "id": "S03", "bar": 3, "role": "VERSE", "t_start": 6.5, "t_end": 9.7, "uses_character": false, "lyric_anchor": "Oscars, the playoffs, the rate cut, the chain", "caption": "Oscars, the playoffs / the rate cut, the chain", "keyframe_prompt": "Quad-split of four tiny felt-and-foam-core nouns: a felt awards envelope, a foam-core basketball hoop, a felt percent-sign rate-cut placard, a felt chain. No character. NO rulers, NO pencils, NO hands, NO human-scale reference objects.", "motion_hint": "Per-quadrant felt-prop micro-motions (envelope flap, ball spin, placard tilt, chain sway). [veo3_1_lite fallback — Seedance flagged ip_detected on the awards shape.]" }, { "id": "S04", "bar": 4, "role": "VERSE", "t_start": 9.7, "t_end": 13.0, "uses_character": true, "lyric_anchor": "You called every shot — and got nothing to gain", "caption": "You called every shot / and got nothing to gain", "keyframe_prompt": "Felt protagonist slumped alone on a felt couch in the SAME felt living room as S02 (room continuity), an empty felt wallet on the cushion. Reflective, deflated.", "motion_hint": "The protagonist slumps deeper, head drops, the felt remote slides off the cushion.", "_note": "Re-rolled once with S02 as a second media ref for room continuity." }, { "id": "S05", "bar": 5, "role": "PRE_DROP", "t_start": 13.0, "t_end": 16.2, "uses_character": false, "lyric_anchor": "They said the floor was closed", "caption": "They said the floor / was closed", "keyframe_prompt": "Miniature foam-core exchange / trading-floor entrance, a felt velvet rope across the door, a tiny foam-core 'MEMBERS ONLY' sign. No character. No readable brand text.", "motion_hint": "The velvet rope sways, the sign flutters. [veo3_1_lite fallback — Seedance nsfw false-positive.]" }, { "id": "S06", "bar": 6, "role": "PRE_DROP", "t_start": 16.2, "t_end": 19.5, "uses_character": false, "lyric_anchor": "Said the smart money already chose", "caption": "Said the smart money / already chose", "keyframe_prompt": "Miniature felt suit-rack: headless felt suits walking past on tiny felt legs (no torsos, no heads). Deadpan. No character.", "motion_hint": "Three felt suits do a stop-motion walk-cycle across frame." }, { "id": "S07", "bar": 7, "role": "PRE_DROP", "t_start": 19.5, "t_end": 22.7, "uses_character": true, "lyric_anchor": "But the smart money's the money in your hand", "caption": "But the smart money's / the money in your hand", "keyframe_prompt": "Macro of the felt protagonist's felt hand cradling the felt Coinbase-Blue phone with the embroidered 'C' arc. Tilt-shift focus on the phone.", "motion_hint": "The thumb taps the felt phone screen twice." }, { "id": "S08", "bar": 8, "role": "PRE_DROP", "t_start": 22.7, "t_end": 26.0, "uses_character": true, "lyric_anchor": "And tonight the floor is wherever you stand", "caption": "And tonight the floor / is wherever you stand", "keyframe_prompt": "Felt protagonist standing on a tiny felt rooftop, a foam-core NYC skyline behind, looking out. Character occupies ~55% of frame, world reads big.", "motion_hint": "The head turns across the skyline, a slow deep breath." }, { "id": "S09", "bar": 9, "role": "INSTRUMENTAL_SWELL", "t_start": 26.0, "t_end": 29.2, "uses_character": true, "lyric_anchor": "(instrumental swell)", "caption": "", "keyframe_prompt": "Felt protagonist at a tiny felt maker-workbench, tiny felt tools around, looking up toward camera as the swell builds. Warm key + practical.", "motion_hint": "One step forward, looks up at camera." }, { "id": "S10", "bar": 10, "role": "CHORUS_A", "t_start": 29.2, "t_end": 32.4, "uses_character": true, "lyric_anchor": "BETS — you called it", "caption": "BETS / you called it", "keyframe_prompt": "Felt protagonist on a tiny felt basketball court, arm extended pointing, a felt basketball mid-air, a foam-core scoreboard reading '+3'. Energy.", "motion_hint": "The arm extends to point, the felt ball spins." }, { "id": "S11", "bar": 11, "role": "CHORUS_A", "t_start": 32.4, "t_end": 35.7, "uses_character": true, "lyric_anchor": "BETS — you called it", "caption": "BETS / you called it", "keyframe_prompt": "Felt protagonist at a tiny foam-core voting booth drawing a felt checkmark on a felt ballot. Decisive.", "motion_hint": "The hand draws a checkmark, the stroke completes." }, { "id": "S12", "bar": 12, "role": "CHORUS_A", "t_start": 35.7, "t_end": 38.9, "uses_character": true, "lyric_anchor": "BETS — you called it", "caption": "BETS / you called it", "keyframe_prompt": "Felt protagonist on a tiny foam-core awards stage opening a felt envelope, a small felt-confetti puff. Smiling.", "motion_hint": "Opens the envelope, the smile widens." }, { "id": "S13", "bar": 13, "role": "CHORUS_A", "t_start": 38.9, "t_end": 42.2, "uses_character": true, "lyric_anchor": "BETS on the Knicks, on the chair, on the spread", "caption": "on the Knicks / on the chair / on the spread", "keyframe_prompt": "Triple-split: felt basketball / felt chair-seat / foam-core scoreboard '+3', the protagonist small and IN the foreground of the center panel (NOT a silhouette). Cuts-within-a-cut energy.", "motion_hint": "Three independent panel motions, the character holds still in the center panel.", "_note": "A prior version read the silhouette as an '11' jersey — re-roll with 'NOT a silhouette, character in foreground'." }, { "id": "S14", "bar": 14, "role": "BRIDGE", "t_start": 42.2, "t_end": 45.4, "uses_character": true, "lyric_anchor": "WIN on the call you already said", "caption": "WIN / on the call you / already said", "keyframe_prompt": "Felt protagonist leaning into a felt friend at a tiny felt bar, mouthing the call. Warm practical bar lights.", "motion_hint": "Leans into the friend, the friend nods." }, { "id": "S15", "bar": 15, "role": "CLIMAX", "t_start": 45.4, "t_end": 48.6, "uses_character": true, "is_climax": true, "lyric_anchor": "Whoever wins — you called it", "caption": "Whoever wins / you CALLED it", "keyframe_prompt": "CLIMAX HERO. Coinbase-Blue felt confetti rains across the frame, the felt protagonist arms raised, head tilted back, a foam-core skyline behind. Doubles as the character-lock climax angle.", "motion_hint": "CLIMAX: arms lift overhead, head tilts back, Coinbase-Blue confetti rains." }, { "id": "S16", "bar": 16, "role": "CHORUS_B", "t_start": 48.6, "t_end": 51.9, "uses_character": true, "lyric_anchor": "Whoever wins — you called it", "caption": "Whoever wins / you called it", "keyframe_prompt": "Macro of the felt Coinbase-Blue phone filling frame, screen lights up solid Coinbase Blue. Last body shot before the end card.", "motion_hint": "Thumb taps the phone twice, the screen pulses." }, { "id": "END", "bar": 17, "role": "END_CARD", "t_start": 51.9, "t_end": 54.9, "uses_character": false, "lyric_anchor": "(end card — song plays under)", "caption": "", "keyframe_prompt": "END CARD — solid Coinbase-Blue (#0052FF) card, white 'coinbase' wordmark centered + subhead 'Prediction markets · now on Coinbase', composited via cairosvg + PIL (NOT AI-rendered). Logo bug suppressed. Holds 3s with the song under it.", "motion_hint": "Static hold (built by scripts/stitch.py, not i2v)." } ], "logo_bug": { "_comment": "Persistent white Coinbase wordmark burned bottom-left over the 16 body bars, SUPPRESSED on the end card (where the big wordmark dominates). Recolor the black Wikimedia Coinbase SVG to white via cairosvg + PIL. NEVER AI-render brand text.", "engine": "cairosvg+pil", "script": "scripts/stitch.py", "brand_wordmark_svg": "brand-assets/logos/coinbase-wordmark.svg", "position": "bottom-left", "width_px": 220, "margin_px": 56, "suppress_on_end_card": true }, "end_card": { "_comment": "Solid Coinbase-Blue card + white wordmark (from the real SVG) + subhead, composited via cairosvg + PIL — NEVER AI-rendered. Holds 3.0s WITH the song still playing under it (1.85s was too short to read); the song afade-outs 0.5s over the tail so there is NO silent tail. macOS New York Semibold lacks the em-dash glyph (U+2014) — the subhead uses a middle dot ' · ' instead.", "engine": "cairosvg+pil", "script": "scripts/stitch.py", "background": "#0052FF", "brand_wordmark_svg": "brand-assets/logos/coinbase-wordmark.svg", "wordmark_scale": 0.62, "subhead_text": "Prediction markets · now on Coinbase", "subhead_font": "New York", "hold_sec": 3.0, "audio_fade_out_sec": 0.5 }, "audio_mix": { "_comment": "The song is the full audio bed — no VO to duck under. The song plays UNDER the end card and afade-outs over the last 0.5s (master tail), so the video ends WITH the music — no trailing silence.", "loudness_lufs": -14, "climax_beat_id": "S15", "duck_under_vo": false, "audio_over_end_card": true, "audio_fade_out_sec": 0.5, "body_end_s": 51.9, "master_duration_s": 54.9 }, "post_production": { "music": { "default": "on", "note": "the sung supplied/generated track IS the bed (no VO to duck under)" }, "captions": { "default": "on", "note": "VEED-whisper white bold-sans captions in the BOTTOM third (Alignment 2, above the logo bug) from the word timings, no pill, ~4-5 words/cue, stop at the end card" }, "logo_bug": { "default": "on", "note": "persistent white wordmark bottom-left over the body, suppressed on the end card" }, "end_card": { "default": "on", "note": "solid-brand-color PIL end card + real wordmark, song plays under it, afade tail (no silent tail)" } } } -
PIPELINE.md 8.4 KB
# Pipeline — cartoon music-video How `config.example.json` maps to the real production steps. This molecule ships a **config + this map**, not a bundled runner: the worked example (Coinbase "Bet on anything") was produced by the video-orchestrator's per-state steps plus two per-project drivers that live in `clients/coinbase/video-01-music-video-debut/scripts/` (`stitch.py`, `rebuild_captions.py`). Reference those directly, or drive the whole run via `video-orchestrator-with-control-plane`. The six steps run **in order** because each depends on the last: the song sets the timeline (via its BAR GRID), the timeline drives the shot count, the locked character + look pack drive the keyframes, the keyframes seed the clips, the song's word timings drive the captions, and assembly stitches all of it with the logo bug + end card. ## Field → source-script map | Config field | Phase | Source step / script (in the run) | Paid? | |---|---|---|---| | `song.source`, `song.prompt`, `song.structure`, `song.model` | 1 Song | ElevenLabs `music_v1` (or ingest the user mp3) | **PAID** (or user-supplied) | | `song.outputs.word_timings` | 1 Song | native `music_v1` timestamps, OR Groq `whisper-large-v3` word-level on the supplied mp3 | ~$0.10 (Whisper) | | `song.outputs.beats` / `.bars`, `song.bar_duration_s`, `song.time_signature` | 1 Song | `librosa.beat.beat_track` (assume 4/4) → `beats.json` + `bars.json` | free | | `song.recommended_trim_end_s` | 1 Song | `audio/song-ingestion-notes.md` trim analysis | free | | `character.descriptor`, `character.anchor`, `character.angles`, `character.method` | 2 Character | video-orchestrator `lock-character` → `nano_banana_2` anchor + 4 angles → `generated/character-lock/LOCKED.json` | **PAID** | | `look_pack.style_descriptor`, `look_pack.negative_tail`, `look_pack.palette_anchors` | 3 Keyframes | threaded verbatim into every keyframe prompt | (defines cost) | | `tableaux[].keyframe_prompt`, `tableaux[].uses_character`, `keyframe_engine` | 3 Keyframes | `nano_banana_2`, 9:16, one per bar (anchor ref on character shots) → `generated/keyframes/scene-NN.png` | **PAID** | | `tableaux[].motion_hint`, `clip_engine` | 4 Clips | Seedance 2.0 i2v (Veo 3.1 Lite fallback) → `generated/animated/scene-NN.mp4` | **PAID** | | `captions`, `tableaux[].caption`, `captions.accent_words` | 5 Captions | `scripts/rebuild_captions.py` (Whisper tokens re-spelled against the locked lyrics) → `captions/master-final.ass` | free | | `logo_bug` | 5 Assembly | `scripts/stitch.py` `build_logo_bug()` (cairosvg → white PNG) | free | | `end_card` | 5 Assembly | `scripts/stitch.py` `build_endcard()` (cairosvg white wordmark + PIL subhead on a solid card) | free | | `tableaux[].t_start/t_end`, `audio_mix` | 5 Assembly | `scripts/stitch.py` (cut each clip to its bar window, hard-cut concat, mux song under the end card, afade) | free | ## 1. Song → ElevenLabs `music_v1` OR user-supplied (config: `song`) [PAID or ingest] **Lock the song FIRST — it sets the timeline.** Either feed `song.structure` + `song.prompt` to ElevenLabs `music_v1` (`source: generate`), or ingest a user-supplied mp3 (`source: user_supplied`, the Coinbase path — the mp3 landed before the API call could fire). Get word-level timestamps: native from `music_v1`, or Groq `whisper-large-v3` word-level for a supplied track → `audio/word-timestamps.json`. Then `librosa.beat.beat_track` → `audio/beats.json` + `audio/bars.json` (4/4). **The BAR GRID sets the timeline** — one bar = one shot (default); snap every `tableaux[].t_start/t_end` to the bar boundaries. The delivered track often shifts tempo (brief said 124 BPM; the track landed at 73.83 BPM half-time). **Strip `[…]` section markers from the lyric text before any music-gen call** — the model sings them literally ("CTA, two bars"). Trim the degraded outro (`recommended_trim_end_s` = 51.9). ## 2. Character → `nano_banana_2` anchor + angles (config: `character`) [PAID] `lock-character` fires one `nano_banana_2` anchor with the verbatim `character.descriptor` at 9:16, then 4 parallel angle calls off the approved anchor (3q-left, 3q-right, action medium, climax hero — the climax angle doubles as the S15 keyframe). Drift-check hair / wardrobe / brand-color / applique / skin tone → `LOCKED.json`. Drift < 5% → `method: anchor-ref`; drift > 15% → escalate to Higgsfield Soul ID (~$15). The locked anchor job is threaded as the media ref into every character-bearing keyframe. ## 3. Keyframes → `nano_banana_2` (config: `tableaux[].keyframe_prompt`, `look_pack`, `keyframe_engine`) [PAID] Per tableau, build the prompt as `look_pack.style_descriptor` + (if `uses_character`) `character.descriptor` + `tableaux[i].keyframe_prompt` + `look_pack.negative_tail`, and call `nano_banana_2` at 9:16. Character shots pass the locked anchor as `media[role=image]` (pass BOTH the anchor AND the prior keyframe for room continuity — e.g. S04 off S02). Style-only shots (`uses_character: false` — S03, S05, S06) pass no character ref. Parallel batches of 3 (Higgsfield burst-credit reserve). One PNG per bar → `generated/keyframes/scene-NN.png`. The "NO RULERS, NO PENCILS, NO HANDS, NO HUMAN-SCALE REFERENCE OBJECTS" negative is load-bearing — without it the model reads "miniature" by adding a scale-reference prop. Review all N before step 4. ## 4. Clips → Seedance 2.0 i2v (config: `tableaux[].motion_hint`, `clip_engine`) [PAID] Per keyframe, build the prompt as `clip_engine.motion_opener` + `tableaux[i].motion_hint` and call `seedance_2_0` image-to-video, `--duration 4`, 9:16, off the clean keyframe → `generated/animated/scene-NN.mp4`. **Lead with the CHARACTER VERB, not the camera move** — camera-led openers ("slow push-in…") read static; this was the single biggest v1→v4 quality jump. Auto-fall back to `veo3_1_lite` when Seedance returns `ip_detected` (Oscar-trophy shapes) or `nsfw` ("intimate"/"leans toward" false positives — S03, S05). Pre-flight the prompt strategy on scenes 1/mid/climax before firing all N. Batches of 3. ## 5. Captions → `scripts/rebuild_captions.py` (config: `captions`) Reads the song's `word-timestamps.json` and **re-spells the timed tokens against the locked lyrics** (`align_to_lyrics`) — Whisper mishears shouted accents ("BETS" → "Hearts", "WIN" → "Went"); never edit the lyrics to match Whisper. Feed a `lyrics-plain.md` (lyric text only, no bar-grid table) so the parser doesn't ingest 700+ markdown-table "words". It chunks ~4–5 words per cue, times each event, and renders VEED-whisper style — clean white **bold sans** in the **BOTTOM third** (Alignment 2, `margin_v` above the bottom-left logo bug, NO pill, 6px outline, ≥0.9s on screen) — and STOPS captions at `caption_end_s` (= body end = 51.9) so the end card breathes → `captions/master-final.ass`. Never mid-frame (Alignment 5) — it covers the character (fixed 2026-07). NOTE: if the host ffmpeg lacks libass (no `subtitles`/`ass` filter), render the cues as timed PIL PNG overlays composited with ffmpeg `overlay=…:enable='between(t,st,en)'` instead — same bottom placement, no libass dependency. The repeated "you called it" ×3 hook had a Whisper gap — hand-patch the chorus repetitions. ## 6. Logo bug + end card + assembly → `scripts/stitch.py` (config: `logo_bug`, `end_card`, `audio_mix`) - `build_logo_bug()`: cairosvg renders the brand wordmark SVG → recolor to white via PIL → the persistent bottom-left bug (`width_px` 220, `margin_px` 56), burned over the 16 body bars, **suppressed on the end card**. **Never AI-render brand text.** - `build_endcard()`: cairosvg white wordmark (`wordmark_scale` 0.62) + PIL subhead on a solid `end_card.background` (#0052FF) card → a silent 3.0s end-card mp4. macOS New York Semibold lacks the em-dash glyph (U+2014) — the subhead uses a middle dot ` · `. - Assembly (`stitch.py`): cut each body clip to its bar window from `bars.json`, **hard-cut** concat on the bar (the shipped v4 used hard cuts — the tested v5 0.3s xfade read less punchy), burn the logo bug + the caption ASS, append the end card holding 3.0s, mux the song OVER the whole video including the end card with a 0.5s `afade` at the tail (`audio_over_end_card` — no silent tail), loudnorm to −14 LUFS → `edits/master-final.mp4` (1080×1920, 30fps, h264+aac, ~54.9s). Re-cuts (new caption chunking, swapped end card, re-timed windows, logo-bug toggle) reuse the existing keyframes/clips/song and cost **$0** — only steps 1–4 spend. -
README.md 4.3 KB
# render-cartoon-music-video scripts — the FREE assembly `render-cartoon-music-video` is the **deterministic, $0 assembly stage** of the cartoon music-video format. The paid stages (the sung song, the character lock, the N per-bar keyframes, the N Seedance i2v clips) are separate capabilities — `create-music-elevenlabs`, `create-image-fal`, `create-video-fal`. This capability spends nothing: it takes the song + `word-timestamps.json` + `bars.json` + one clip per bar + the brand wordmark SVG and stitches the finished master. Re-cuts (new caption chunking, a swapped end card, re-timed windows, a logo-bug toggle) reuse the existing song / keyframes / clips and cost **$0**. `config.example.json` is the worked example (Coinbase "Bet on anything", ~55s 1080×1920). `PIPELINE.md` maps every config block to its source step. This README documents the FREE assembly pieces that `render-cartoon-music-video` owns. ## 1. Captions — from the song's word timings, re-spelled against the locked lyrics Captions come from the song's `word-timestamps.json` **re-spelled against the locked lyrics** (`align_to_lyrics`) — Whisper mishears shouted accents ("BETS" → "Hearts", "WIN" → "Went"), so the timed tokens are re-spelled against the locked lyric file (never edit the lyrics to match Whisper). Feed a `lyrics-plain.md` (lyric text only, no bar-grid table) so the parser doesn't ingest hundreds of markdown-table "words". It chunks ~4–5 words per cue and renders VEED-whisper style — clean white **bold sans** in the **BOTTOM third** (Alignment 2, `margin_v` above the bottom-left logo bug so they don't collide, NO pill, 6px outline), ≥0.9s on screen — and STOPS captions at the end-card boundary so the brand frame breathes. Do **not** place captions mid-frame (Alignment 5) — it covers the character (fixed 2026-07). Matches the `ugc-walk-and-talk` recipe's `add-captions-veed-fal --preset whisper --position bottom` treatment. ## 2. Cut-to-bar + hard-concat on the bar The song's BAR GRID (librosa beat-track, 4/4) sets the timeline — one bar = one shot (default). Assembly cuts each body clip to its bar window from `bars.json` and hard-concats **on the bar** (the shipped cut used hard cuts — a tested 0.3s xfade read less punchy). No dissolves. The climax clip is timed so the payoff line lands on the drop. ## 3. Logo bug + end card — cairosvg + PIL from the real wordmark, no AI text - **Logo bug:** cairosvg renders the brand wordmark SVG → recolored white via PIL → the persistent bottom-left bug burned over the body bars, **suppressed on the end card**. - **End card:** cairosvg white wordmark + PIL subhead on a solid brand-color card → a silent ~3s end-card mp4. On macOS pick a font with the em-dash / middle-dot glyph (use ` · `). The brand text is **never** AI-rendered — a diffusion model garbles a wordmark. ## 4. FFmpeg composite FFmpeg stitches the master: cut each body clip to its bar window, hard-concat on the bar, burn the logo bug (bottom-left) + the captions, append the end card holding ~3s, mux the song OVER the whole video including the end card with a 0.5s `afade` at the tail (no silent tail), and `loudnorm I=-14`. Captions burn via libass **if present**; on a host without libass/drawtext (common — Homebrew ffmpeg), render each caption cue AND the logo bug as timed **PIL PNG overlays** (`overlay=x:y:enable='between(t,st,en)'`) at the same placement. The sung song IS the bed — no separate VO. Output is a 1080×1920 h264 + aac master. Deterministic, no paid calls, no keys. ## 5. QC per scene before publish (the two content failure modes) The assembly is faithful, but the upstream keyframe/i2v can slip two defects that a sparse-still watch misses in a ~55s master: - **Character drift felt → smooth-3D "man"** partway through (lives in the KEYFRAME; regenerate the drifted bar's keyframe, don't re-cut). - **Hallucinated hands** — realistic fingers in a hand macro, a pointing finger on a "tap the phone" shot, or a disembodied hand from the frame edge. Extract a **2 fps** contact sheet across the whole master + a per-bar **FACE** crop and **HAND** crop (across each clip's full duration); audit ALL bars at head-crop resolution to find the true drift boundary; then re-extract frames from the **served** bytes (`get_download_url`), not the local file, after publish. (cartoon-music-video, Figma run 2026-07-18.)
-
-
tests
-
smoke-test.md 1 KB
# Smoke Test Given the sung song (mp3 + bar grid + word timings), one animated/craft keyframe + one i2v clip per bar with ONE recurring locked character, and the persistent logo bug, `render-cartoon-music-video` assembles the master: cut each clip to its bar, hard-cut on the bar, burn big-serif captions, overlay the logo bug, close on a solid-brand-color end card, mux the song → 1080×1920 h264+aac (~55s). Pass when the assembly runs to a valid MP4 and: - one shot per bar, hard-cut on the bar (no crossfades); the ONE locked character holds across every tableau; - captions are big-serif re-spelled against the locked lyrics (no pill), the logo bug persists; - the end card is a solid brand-color card, song carrying under it with an afade tail (no silent tail); - the sung song is the entire script — no separate VO; - **no paid call is made** — the song, keyframes, and clips come from the paid capabilities (create-music-elevenlabs / create-image-fal / create-video-fal); this assembly is $0 and a re-cut reuses the existing assets.
-
-
SKILL.md 5.3 KB
--- name: render-cartoon-music-video description: Assemble a cartoon / animated / hand-crafted music-video ad from a config — a sung song carries the whole narrative while N per-bar i2v clips (one recurring animated character, one look pack) are each cut to their BAR window from librosa beat-tracking and hard-concatenated on the bar, VEED-whisper white bold-sans captions in the BOTTOM third (Alignment 2, above the logo bug, no pill) burned from the song's word timings re-spelled against the locked lyrics, a persistent brand logo bug held over the body (suppressed on the end card), and closed on a solid-brand-color PIL end card with the song still playing under it — never AI-rendered text. This is the FREE deterministic assembly stage (cut-to-bar + hard concat + logo bug + captions + end card + song mux); the song, character, keyframes, and clips come from create-music-elevenlabs / create-image-fal / create-video-fal. Use for the cartoon-music-video format. status: active --- # render-cartoon-music-video Assemble a **cartoon music-video** ad from a config: an animated / illustrated / hand-crafted story (stop-motion felt-and-foam, claymation, 2D toon, cut-out) where a sung song is the entire script and one recurring animated character carries every shot, cut to the bar with the climax line on the drop. This capability is the **FREE, deterministic assembly** — cut-to-bar, hard-concat, the logo bug + captions burn, and the solid-color end card. `scripts/config.example.json` is the worked example (Coinbase "Bet on anything", ~55s 1080×1920 9:16, 16 body bars + a 3s end card); `scripts/PIPELINE.md` maps every config block to its source step and `scripts/README.md` documents the free assembly. ## Run This is the **FREE, deterministic** assembly stage — it spends nothing. The paid inputs are separate capabilities: the sung song (`create-music-elevenlabs`, or a user-supplied mp3 + Whisper word timings) beat-tracked with librosa so the BAR GRID sets the timeline; one locked recurring character + one keyframe per bar in one look pack (`create-image-fal`, Nano Banana); and one Seedance i2v clip per bar (`create-video-fal`). Given the song + `word-timestamps.json` + `bars.json` + one clip per bar + the brand wordmark SVG, `render-cartoon-music-video` cuts each clip to its bar window, hard-concats on the bar, burns the logo bug + captions, appends the solid-color end card, and muxes the song under it → the master. Re-cuts reuse the existing song / keyframes / clips and cost **$0**. ## Contract (the free assembly) - **The sung song carries the narrative — no separate VO.** The generated/supplied track IS the bed (no VO to duck under); do not add a spoken voiceover or a second bed. - **Plan the timeline AROUND the delivered song's BAR GRID.** Beat-track the song with librosa (assume 4/4); one bar = one shot (default). Snap every tableau window to the bar boundaries — never trim the song to a pre-planned grid. - **Captions from the song's word timings, re-spelled against the locked lyrics.** Whisper mishears shouted accents ("BETS" → "Hearts"); re-spell the timed tokens against the locked lyric file (never edit lyrics to match Whisper). VEED-whisper white **bold sans** in the **BOTTOM third** (Alignment 2, `margin_v` above the bottom-left logo bug), ~4–5 words per cue, NO background pill; captions STOP at the end-card boundary. Never mid-frame — it covers the character (fixed 2026-07). If the host ffmpeg lacks libass, render the cues as timed PIL PNG overlays (ffmpeg `overlay=…:enable='between(t,st,en)'`) at the same bottom placement. - **Land the climax line on the drop.** The climax tableau is timed so the payoff line sits on the sub-bass drop; accent that line. - **Persistent logo bug, suppressed on the end card.** Burn a white brand wordmark bottom-left over the body bars (cairosvg → PIL from the real SVG), suppressed on the end card where the big wordmark dominates. - **End card via cairosvg + PIL from the real wordmark — never AI-render brand text.** Solid brand-color card + white wordmark + subhead, holding ~3s WITH the song still playing under it (afade-out over the tail — no silent tail). A diffusion model garbles a wordmark. - **FFmpeg composite, deterministic, FREE.** Cut each clip to its bar window, hard-concat, burn the logo bug + captions, append the end card, mux the song over the whole video with a 0.5s afade tail, `loudnorm I=-14` → a 1080×1920 h264+aac master. No paid calls, no keys. When the host ffmpeg lacks libass/drawtext, BOTH the captions and the logo bug are timed **PIL PNG overlays** (`overlay=x:y:enable='between(t,st,en)'`), not an ASS burn. - **QC PER SCENE, never just the master — the two failure modes are content, not assembly.** The upstream keyframe/i2v steps can (a) drift the character **felt → smooth-3D "man"** partway through and (b) hallucinate hands — realistic fingers in a hand macro, a **pointing finger** on a "tap the phone" shot, or a **disembodied hand** sliding in from the frame edge. Both hide at thumbnail size. Extract a **2 fps** contact sheet + a per-bar **FACE** crop and **HAND** crop (across each clip's full duration), confirm every bar is matte felt with the ONE character and no human/floating hands, and re-check the **served** bytes after publish. A drifted bar means regenerating that bar's **KEYFRAME** (not re-cutting) — see the recipe STEP 3/6. -
skill.meta.json 329 B
{ "slug": "render-cartoon-music-video", "category": "capabilities", "domain": "ads", "tags": [ "ads" ], "installation": { "base_command": "npx goose-skills install render-cartoon-music-video", "supports": [ "claude", "cursor", "codex" ] }, "requires_skills": [ "watch" ] }
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.