{"id":"render-3d-character-explainer","name":"render-3d-character-explainer","summary":"「NタイプのX」リスト表紙を基に、繰り返し登場する人間の主人公とN人のペルソナキャラクターのロックキャスト、リストアイテムごとに1人ずつ、光沢のある3Dキャラクターアニメーション解説動画広告(~77秒、9分16秒)を組み立てましょう。","body":"# render-3d-character-explainer\n\nThe free, deterministic renderer for the **3d-character-explainer** video ad format — the\nglossy Pixar-style 3D spot built on an **\"N types of X\" listicle** spine, where a recurring\nhuman protagonist plus a locked cast of **N persona characters (one per list item)** carry a\nhook → \"deeper story\" → cast-reveal → one beat per list item → kicker → product test →\nrelieved payoff. This capability is the **FREE assembly stage only**. All generative work\n(Nano-Banana cast anchors + per-scene keyframes, Kling-V3 i2v clips, ElevenLabs VO + music,\nor a source ad's audio reused verbatim) happens upstream in the recipe and is handed to this\ncapability as files.\n\nIt ports the validated compose recipe from the Bristle \"Six Types\" restyle run\n(`_render_full.sh` — per-scene trim → normalize 1080×1920/fps30 → concat -c copy → mux the\nsource audio, with a static-still fallback on any failed clip). The assembly is\ndeterministic — iterate the cut for free, re-roll only the offending paid beat.\n\n## Two modes\n\n- **Restyle mode** (`audio_mode: \"restyle\"`, the reference run) — re-tell a finished source\n  ad, beat for beat, as 3D character comedy. The source ad's **audio mix (VO + music bed) is\n  reused VERBATIM** (`source_audio`), and the per-scene `target_sec` table is inherited from\n  the source's scene timing. No new VO or music is rendered. The trims must sum to the source\n  audio length.\n- **Original mode** (`audio_mode: \"original\"`) — the ad authors its own narration. Each scene\n  carries a measured VO cue (`scenes[].vo`, `target_sec` = the ffprobe'd VO duration) which is\n  concatenated into a VO track (loudnorm I=-14) and optionally mixed under a `music_bed`\n  (loudnorm I=-26 then `volume`, `amix normalize=0`).\n\n## What it does (the deterministic recipe)\n\n1. **Per-scene retime.** Each i2v clip is trimmed to its scene `target_sec` and normalized to\n   identical dims/fps/SAR\n   (`scale=W:H:force_original_aspect_ratio=decrease,pad=W:H:(ow-iw)/2:(oh-ih)/2:color=<pad>,fps=30,setsar=1`).\n   A clip **shorter** than its window is extended with `tpad=stop_mode=clone`; a longer one is\n   `-t` trimmed. Decrease+pad (never crop) preserves the full 9:16 keyframe framing.\n2. **Static-still fallback.** For any scene whose `clip` is missing or failed to render, the\n   scene's `keyframe` PNG is looped (`-loop 1`) for `target_sec`, so the master always\n   assembles. Fallback scenes are printed at the end.\n3. **Identical re-encode + concat.** Every segment is re-encoded `libx264 -crf 18 -pix_fmt\n   yuv420p -r 30` even if already correct — a dims/framerate mismatch makes the concat demuxer\n   silently drop frames — then concatenated via the concat demuxer (`-c copy`).\n4. **Audio.** Restyle: `source_audio` muxed verbatim (`-map 0:v -map 1:a`), clamped to the\n   video length. Original: per-scene VO track (optional `atempo`, `apad`, `-t` clamp) → loudnorm\n   → optionally mixed under the music bed.\n5. **Captions last.** `make_captions.py` emits a libass `.ass` (one cue per scene, `start =\n   scene_start + 0.08s`, suppressed on any scene with no caption — e.g. a product/end-card beat\n   carrying its own typeset copy). `compose.py` burns it as the final filter so captions sit on\n   top. Word-level energy-pop captions (Whisper on the narration) are the recipe's upstream\n   option — produce that `.ass` externally and point `captions_ass` at it; compose burns\n   whatever `.ass` it's handed.\n\n## Scripts (free — Python + ffmpeg, no bash, no paid calls)\n\n- `scripts/make_captions.py` — emits the per-scene libass `.ass` from the SAME scene table\n  compose reads, so caption windows stay in lockstep with the cut. Run before `compose.py`\n  (or leave `captions_ass` unset / pointing at nothing to skip captions).\n- `scripts/compose.py` — the assembler: per-scene trim + identical 1080×1920/30fps re-encode\n  (static-still fallback on missing clips) → concat → audio (restyle verbatim / original\n  mix) → burn captions → master mp4.\n- `scripts/config.example.json` — the shape of the `config` the recipe binds (the\n  brand-neutralised \"Six Types\" restyle values as a worked reference).\n\n## Inputs (all via `--config` + a runtime work dir — NO hardcoded paths)\n\n`config.json` carries: `audio_mode` (`restyle` | `original`), `scenes[]` (each `{id, clip,\nkeyframe, target_sec, caption?, vo?, atempo?}` where `target_sec` is the source-inherited\nwindow in restyle mode or the **measured** VO window in original mode, and `keyframe` is the\nstatic-still fallback source), `source_audio` (restyle), `music_bed` + `music_volume` +\n`atempo` (original), `width`/`height` (default 1080×1920), `pad_color` (letterbox colour),\n`captions_ass`, and `caption_style`. See `config.example.json`.\n\n## Craft rules (load-bearing — faithful to the source molecule + reference run)\n\n- **Restyle inherits the source timing.** A restyle reuses the source ad's exact audio, scene\n  order, and per-beat durations verbatim; only an original-mode remix authors its own VO +\n  timing table. Merge any sub-1.5s flash scene into a neighbour upstream to avoid a dead\n  micro-cut (the reference folded scene 7 into scene 8).\n- **Normalize decrease+pad, never crop** — the listicle's cast-reveal + per-persona framing\n  must not lose edges; letterbox-pad to the canvas colour instead. Re-encode every segment to\n  30fps before concat, even if already correct, or the concat demuxer silently drops frames.\n- **Static-still fallback is mandatory** — Kling can 403 mid-run (a billing wall after a burst\n  of successes, not a rate limit). Any failed clip loops its keyframe so the master still\n  assembles; re-roll only the missing beat and recompose (free).\n- **`generate_audio` was false upstream** — Kling would otherwise invent its own dialog track;\n  the real narration is muxed here separately. (This is the recipe's upstream call, not this\n  capability.)\n- **No AI-rendered brand text** — the product-beat keyframe shows a BLANK-label box; the real\n  wordmark/end-card copy is composited upstream, never AI-drawn. Suppress captions on any\n  product/end-card beat (its typeset copy carries the message — two text layers at one spot are\n  both unreadable).\n- **Caption `start = scene_start + 0.08s`** (avoids the caption flashing a frame before a cut).\n\n## Requires\n\n`watch` (QC the final master — the human protagonist reads as the SAME person every scene\n(wardrobe/hair/lighting held), each persona is on-model, the cast-reveal lineup matches the N\nlist items, the product beat shows the REAL box, narration lands beat-for-beat, and duration is\nwithin ±0.1s of the summed windows). The recipe gates the paid `create-image-fal` (cast\nanchors + keyframes), `create-video-fal` (Kling-V3 i2v), `create-vo-elevenlabs`, and\n`create-music-elevenlabs` calls to their own capabilities — this capability itself makes NO\npaid calls.","author":"@gooseworks-ai","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/gooseworks-ai/goose-skills/tree/main/skills/ads/capabilities/render-3d-character-explainer","license":"MIT","category":"writing","lang":"en","tokens":1842,"stars":0,"calls30d":2,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"scripts/compose.py","size":11746,"sha256":"83d2d1ec6cfa383d872ed9225f19b711bee2bdf0a96b982fe55eda8f2876ec87"},{"path":"scripts/config.example.json","size":4016,"sha256":"3f8156abe13659aaa7688eb4ee256c9962d53b52fc8c5341446d008f3ce03932"},{"path":"scripts/make_captions.py","size":3278,"sha256":"e10a76b8fb685b1c0e5e0fea97014f75ff13a7f0c9e36871daa2b3cfeda5e2b1"},{"path":"skill.meta.json","size":335,"sha256":"b92ea33ef16bb45305fcf41e6f17bebdb748ef8598cc8cc378add068eda703c6"},{"path":"tests/smoke-test.md","size":4284,"sha256":"c929580635d940248dd3e699349da8c38733d68f34bf644c8dd2b1d32b99bd61"}],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":true,"networkEndpoints":[]}}