Claude Skill

render-brand-identity-reveal

Render a 'brand identity reveal' video from a config — a single poster frame in a real, softly-lit space (real wall, soft-focus plant in the corner, dappled leaf shadow, illuminated poster) whose artwork HARD-CUTS through ~10 on-brand poster mockups (hero product, IG post, hangin

LLM Mart · 0 points · 3 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download gooseworks-ai-goose-skills-skills_ads_capabilities_render-brand-identity-reveal-e1592ee.zip · 17 KB
Part of gooseworks-ai/goose-skills — 44 skills

Install

skills CLI npx skills add https://github.com/gooseworks-ai/goose-skills/tree/main/skills/ads/capabilities/render-brand-identity-reveal
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install gooseworks-ai-goose-skills@llmmart
Git 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-brand-identity-reveal

Render the 'brand identity reveal' format from a config. The signature is a single fixed poster frame in a REAL, softly-lit space (the illuminated poster-frame look of a boutique/cinema): a real wall, a real soft-focus plant in the lower-left corner, dappled leaf shadow. The artwork INSIDE the frame HARD-CUTS (no crossfade) through 11 beats — 10 on-brand poster mockups + a brand END CARD held ~3s. Music bed only, NO voiceover; copy is baked into each mockup, never overlaid as captions.

This capability is the FREE assembly only. The paid parts are separate generic capabilities the recipe names — create-image-fal (the one-shot environment plate) and create-music-elevenlabs (the bed). Never re-implement them here.

Three layers

  1. Environment plate (PAID, create-image-fal, flux-pro ultra, 9:16) — an empty lit poster frame in a real space, high-res so the camera can be pushed closer by cropping. Pick a wall color that makes the brand's poster colors POP (complement of the dominant hue).
  2. Mockups (FREE) — real-DOM HTML/CSS (scene.html), one poster per beat, built from the brand's real assets + approved copy, frame-stepped via Playwright (render_art.py, bare mode, device_scale_factor 2).
  3. Composite (FREE) — measure_frame.py detects the blank poster interior quad; composite.py perspective-warps each poster into it AND multiplies the plate's real leaf-shadow/light back onto the poster (so it reads as behind glass) + a glass sheen; build_video.sh sequences the frames with the music bed.

Inputs

  • config.json — copy scripts/config.example.json and edit (canvas, plate prompt + wall_style, camera crop, per-beat durations, end-card copy). Schema + per-file working-dir layout are documented in scripts/PIPELINE.md.
  • The brand's REAL product/packaging/lifestyle stills, wordmark, and icon (recreate the icon as an SVG <mask> if the only source has an occluding element). Approved copy only.

Workflow (free assembly)

CAP=skills/ads/capabilities/render-brand-identity-reveal
RUN=<project>/working    # holds scene.html + assets/ + the create-image-fal plate in bg/

python3 $CAP/scripts/recrop.py 0.72 0.13      # camera distance (bigger frac = bigger frame)
python3 $CAP/scripts/measure_frame.py         # detect the blank poster interior quad
python3 $CAP/scripts/render_art.py            # render each poster standalone (Playwright, dsf 2)
python3 $CAP/scripts/composite.py             # warp into frame + shadow multiply + sheen
bash    $CAP/scripts/build_video.sh $RUN/concat.txt $RUN/music.mp3 out.mp4 13.05 1.0

Then watch the master (music-only, every poster legible, shadow falls across the art, end card holds). See scripts/PIPELINE.md for adapting scene.html per brand.

Rules

  • Approved brand copy ONLY — never invent claims, customers, results, or testimonials.
  • Toggle beats with OPACITY, not display (a per-state display:flex silently overrides a display:none toggle and paints one state over all others).
  • Camera distance = re-crop the high-res plate (recrop.py), don't regenerate.
  • The realism trick = MULTIPLY the plate's real shadow/light back onto each composited poster.
  • Music bed only, no VO. Close on the brand end card.

Failure Modes

  • Every rendered frame identical → a per-beat display: rule beat the visibility toggle; use opacity.
  • Playwright evaluate(fn, arg) didn't switch state in a loop → bake the beat index into the JS string and return the applied state to assert it.
  • Composited poster looks pasted-on → you skipped the shadow multiply (shadow_strength ~0.85).
  • Studio product won't cut out (white cap == seamless bg) → present as a photo-tile, don't corner-flood-fill.
  • Playwright missing under node → use the python playwright + cached chromium.
Files (goose-skills)
  • scripts
    • build_video.sh 1.2 KB
      #!/usr/bin/env bash
      # Free assembly step: sequence the composited poster frames + a music bed into the master.
      # Inputs: a concat list (frames + per-beat durations) and a music file. No paid calls.
      #
      #   ./build_video.sh <concat.txt> <music.mp3> <out.mp4> <total_seconds> [music_start]
      #
      # concat.txt is an ffmpeg concat-demuxer list of the composited frames (frames_v2/frame_NN.png)
      # with `duration <s>` lines (variable, non-uniform rhythm); repeat the last frame line once so
      # its duration is honored. See config.example.json -> beats[].seconds for the pacing.
      set -euo pipefail
      CONCAT="${1:?concat list}"; MUSIC="${2:?music}"; OUT="${3:?out.mp4}"
      DUR="${4:?total seconds}"; MSTART="${5:-1.0}"
      FADE=$(python3 -c "print(max(0, ${DUR} - 0.35))")
      
      ffmpeg -y -hide_banner -loglevel error \
        -f concat -safe 0 -i "$CONCAT" \
        -ss "$MSTART" -i "$MUSIC" \
        -filter_complex "[0:v]fps=30,scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1,format=yuv420p[v];\
      [1:a]atrim=0:${DUR},asetpts=PTS-STARTPTS,afade=t=out:st=${FADE}:d=0.35,loudnorm=I=-14:TP=-1.5:LRA=11[a]" \
        -map "[v]" -map "[a]" -t "$DUR" \
        -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p \
        -c:a aac -b:a 192k -movflags +faststart \
        "$OUT"
      echo "wrote $OUT (${DUR}s)"
      
    • composite.py 3 KB
      #!/usr/bin/env python3
      """Composite each standalone artwork into the lit poster frame of the photoreal plate.
      Reintroduces the plate's real leaf-shadow/lighting onto the poster + adds glass sheen."""
      import os, ast
      import numpy as np
      from PIL import Image, ImageDraw, ImageFilter
      
      HERE = os.path.dirname(os.path.abspath(__file__))
      PLATE = Image.open(os.path.join(HERE, "bg/plate_final.png")).convert("RGB")
      W, H = PLATE.size
      c = ast.literal_eval(open(os.path.join(HERE, "bg/corners.txt")).read())
      # inset 4px so artwork never bleeds onto the wood frame
      def inset(p, dx, dy): return (p[0] + dx, p[1] + dy)
      TL = inset(c["tl"], 4, 4); TR = inset(c["tr"], -4, 4)
      BR = inset(c["br"], -4, -4); BL = inset(c["bl"], 4, -4)
      QUAD = [TL, TR, BR, BL]
      
      OUT = os.path.join(HERE, "frames_v2"); os.makedirs(OUT, exist_ok=True)
      
      def find_coeffs(output_pts, input_pts):
          A = []
          for (xo, yo), (xi, yi) in zip(output_pts, input_pts):
              A.append([xo, yo, 1, 0, 0, 0, -xi * xo, -xi * yo])
              A.append([0, 0, 0, xo, yo, 1, -yi * xo, -yi * yo])
          A = np.array(A, dtype=np.float64)
          B = np.array(input_pts, dtype=np.float64).reshape(8)
          return np.linalg.solve(A, B)
      
      # --- shadow/light map from the empty plate interior (canvas space) ---
      plate_np = np.asarray(PLATE).astype(np.float32)
      luma = 0.2126 * plate_np[..., 0] + 0.7152 * plate_np[..., 1] + 0.0722 * plate_np[..., 2]
      # reference "fully-lit" white = high percentile inside the quad bbox
      xs = [p[0] for p in QUAD]; ys = [p[1] for p in QUAD]
      sub = luma[min(ys):max(ys), min(xs):max(xs)]
      ref = np.percentile(sub, 97)
      mapf = np.clip(luma / max(ref, 1.0), 0.25, 1.0)
      mapf = 1.0 - (1.0 - mapf) * 0.85          # 0.85 = shadow strength
      mapf = np.stack([mapf] * 3, axis=-1)       # HxWx3
      
      # glass sheen (soft diagonal highlight) + edge inner shadow, built once in quad space
      sheen = Image.new("L", (W, H), 0)
      sd = ImageDraw.Draw(sheen)
      sd.polygon(QUAD, fill=0)
      # a bright band across the upper-left of the poster
      band = Image.new("L", (W, H), 0)
      bd = ImageDraw.Draw(band)
      bx0, by0 = TL; bx1 = TR[0]
      bd.polygon([(bx0, by0), (bx0 + (bx1 - bx0) * 0.55, by0),
                  (bx0, by0 + (BL[1] - by0) * 0.62)], fill=42)
      band = band.filter(ImageFilter.GaussianBlur(70))
      sheen_np = np.asarray(band).astype(np.float32)[..., None]
      
      def coeffs_for():
          inp = [(0, 0), (1480 - 1, 0), (1480 - 1, 2136 - 1), (0, 2136 - 1)]
          return find_coeffs(QUAD, inp)
      
      COEFFS = coeffs_for()
      
      for i in range(1, 12):
          art = Image.open(os.path.join(HERE, f"art/art_std_{i:02d}.png")).convert("RGBA")
          warped = art.transform((W, H), Image.PERSPECTIVE, COEFFS, Image.BICUBIC, fillcolor=(0, 0, 0, 0))
          wnp = np.asarray(warped).astype(np.float32)
          rgb = wnp[..., :3]; alpha = wnp[..., 3:4] / 255.0
          rgb = rgb * mapf                       # bake real leaf-shadow onto poster
          rgb = np.clip(rgb + sheen_np, 0, 255)  # glass sheen highlight
          out = plate_np.copy()
          out = out * (1 - alpha) + rgb * alpha  # alpha composite over plate
          Image.fromarray(out.astype(np.uint8)).save(os.path.join(OUT, f"frame_{i:02d}.png"))
          print("wrote", f"frame_{i:02d}.png")
      
    • config.example.json 3.5 KB
      {
        "canvas": { "width": 1080, "height": 1920, "fps": 30 },
      
        "plate": {
          "_comment": "PAID: generated once via create-image-fal (fal-ai/flux-pro/v1.1-ultra). Empty lit poster frame in a real space. Pick a wall color that makes the brand's poster colors POP (complement of the dominant brand hue).",
          "model": "fal-ai/flux-pro/v1.1-ultra",
          "aspect_ratio": "9:16",
          "wall_style": "soft sage-green glossy subway tiles with fine grout lines",
          "prompt": "Photorealistic vertical 9:16 photograph, cinematic soft light, shallow depth of field. A single empty poster frame mounted flat on a wall, viewed nearly straight-on with only slight perspective. Slim brushed/wood snap poster frame with a thin bevel and soft drop shadow. The poster panel inside is a BLANK plain matte off-white surface, evenly and brightly lit like an illuminated lightbox, completely empty with NO text/logo/image. Lower-left foreground: a real potted plant with green leaves thrown heavily out of focus (creamy bokeh), leaves rising to about the bottom edge of the frame. Gentle dappled leaf shadows on the wall upper area. Premium boutique interior, warm inviting light, subtle vignette. {wall_style}."
        },
      
        "camera": {
          "_comment": "Frame size in the composition. recrop.py <interior_width_frac> <top_frac>. Larger frac = closer camera / bigger frame. 0.72 = frame fills ~72% of width.",
          "interior_width_frac": 0.72,
          "top_frac": 0.13
        },
      
        "composite": {
          "_comment": "composite.py: perspective-warp each mockup into the detected frame quad, MULTIPLY the plate's real leaf-shadow/light back onto the poster (shadow_strength), add glass sheen.",
          "shadow_strength": 0.85,
          "sheen": true
        },
      
        "beats": [
          { "id": 1,  "kind": "hero-product",        "seconds": 1.05, "note": "real product on brand-color bg + ghosted brand icon" },
          { "id": 2,  "kind": "social-post",         "seconds": 0.95, "note": "IG-post mockup: product/lifestyle image + approved caption" },
          { "id": 3,  "kind": "hanging-banners",     "seconds": 0.95, "note": "two banners: wordmark + product + 3-word descriptor" },
          { "id": 4,  "kind": "sticker-sheet",       "seconds": 1.05, "note": "repeating brand-icon badge grid + one peel sticker" },
          { "id": 5,  "kind": "logo-lockup",         "seconds": 1.00, "note": "icon + wordmark + tagline" },
          { "id": 6,  "kind": "poster",              "seconds": 1.05, "note": "editorial hero still + approved headline + URL" },
          { "id": 7,  "kind": "lifestyle-a",         "seconds": 0.95, "note": "real lifestyle still, full-bleed" },
          { "id": 8,  "kind": "packaging",           "seconds": 0.95, "note": "real packaging hero + short label" },
          { "id": 9,  "kind": "lifestyle-b",         "seconds": 1.00, "note": "second real lifestyle still, full-bleed" },
          { "id": 10, "kind": "big-icon",            "seconds": 1.10, "note": "large brand icon sign-off on brand color" },
          { "id": 11, "kind": "end-card",            "seconds": 3.00, "note": "BRAND END CARD: icon + wordmark + tagline + what the brand makes + Shop Now + URL. Held long." }
        ],
      
        "music": {
          "_comment": "PAID (optional, default ON): create-music-elevenlabs, instrumental. Reference is music-bed-only, NO VO.",
          "direction": "upbeat playful pop / soft house, 90-120 BPM, instrumental, beauty-brand Reels energy",
          "start_offset_s": 1.0
        },
      
        "end_card": {
          "tagline": "<brand tagline>",
          "descriptor": "<one approved descriptor line>",
          "product_lines": "<what the brand makes, e.g. 'hand mist · body & hair mist · glow essence'>",
          "cta": "Shop Now",
          "url": "<brand.com>"
        }
      }
      
    • measure_frame.py 1.7 KB
      #!/usr/bin/env python3
      """Detect the bright blank poster interior in the plate and report its 4 corners."""
      import numpy as np
      from PIL import Image, ImageDraw
      
      im = Image.open("bg/plate_final.png").convert("RGB")
      a = np.asarray(im).astype(np.int32)
      R, G, B = a[..., 0], a[..., 1], a[..., 2]
      mx = a.max(2); mn = a.min(2)
      val = mx
      sat = np.where(mx > 0, (mx - mn) / np.maximum(mx, 1), 0)
      # poster interior: bright, low saturation, near-neutral (not green wall, not tan frame)
      mask = (val > 150) & (sat < 0.16) & (np.abs(R - G) < 26) & (np.abs(G - B) < 26)
      
      # keep the largest connected component via simple flood using scipy if present, else bbox of mask
      try:
          from scipy import ndimage
          lbl, n = ndimage.label(mask)
          if n:
              sizes = ndimage.sum(np.ones_like(lbl), lbl, range(1, n + 1))
              biggest = int(np.argmax(sizes)) + 1
              mask = lbl == biggest
      except Exception as e:
          print("no scipy, using raw mask:", e)
      
      ys, xs = np.where(mask)
      s = xs + ys
      d = xs - ys
      tl = (xs[np.argmin(s)], ys[np.argmin(s)])
      br = (xs[np.argmax(s)], ys[np.argmax(s)])
      tr = (xs[np.argmax(d)], ys[np.argmax(d)])
      bl = (xs[np.argmin(d)], ys[np.argmin(d)])
      print("bbox:", xs.min(), ys.min(), xs.max(), ys.max())
      print("TL", tl, "TR", tr, "BR", br, "BL", bl)
      
      # draw for verification
      g = im.copy(); dr = ImageDraw.Draw(g)
      for p, c in [(tl, (255, 0, 0)), (tr, (0, 120, 255)), (br, (255, 0, 255)), (bl, (0, 200, 0))]:
          dr.ellipse([p[0]-10, p[1]-10, p[0]+10, p[1]+10], fill=c)
      dr.line([tl, tr, br, bl, tl], fill=(255, 0, 0), width=3)
      g.save("bg/plate_corners.png")
      with open("bg/corners.txt", "w") as f:
          f.write(repr({"tl": tl, "tr": tr, "br": br, "bl": bl}))
      print("saved plate_corners.png")
      
    • PIPELINE.md 3.2 KB
      # PIPELINE — brand-identity-reveal free assembly
      
      The free renderer is **documentation-grade**: `scene.html` is brand-specific (the shipped
      reference is the Touchland instance), and the Python scripts drive Playwright + PIL + FFmpeg
      around it. Adapt `scene.html` per brand; the scripts are generic.
      
      ## Working-dir layout (per project)
      
      ```
      working/
        scene.html            # 11 poster modules; one active via `.on` class (toggle by OPACITY)
        assets/products/*     # the brand's real product / lifestyle stills
        assets/brand/*        # wordmark png, brand-icon svg
        bg/plate_sage_1.png   # PAID: create-image-fal environment plate (high-res, empty frame)
        bg/plate_final.png    # recrop.py output (camera distance)
        bg/corners.txt        # measure_frame.py output (the frame interior quad)
        art/art_std_01..11.png# render_art.py output (standalone posters, bare mode, 2x)
        frames_v2/frame_01..11.png  # composite.py output (posters warped into the lit frame)
        concat.txt            # ffmpeg concat list (frames + per-beat durations)
        music.mp3             # PAID: create-music-elevenlabs bed
      ```
      
      ## Steps (script → output)
      
      1. **Plate [PAID]** — `create-image-fal` (flux-pro ultra, 9:16) → `bg/plate_sage_1.png`.
         `gen_plate.py` is the reference wrapper (loads FAL_KEY, prompt in `config.plate`). Pick a
         `wall_style` color that complements the brand's dominant hue so the posters pop.
      2. **Camera** — `recrop.py <interior_width_frac> <top_frac>` → `bg/plate_final.png`. The plate
         is high-res, so cropping closer costs no quality. `0.72 0.13` = frame ~72% of width.
      3. **Detect frame** — `measure_frame.py` → `bg/corners.txt` (bright + low-sat + near-neutral
         mask → largest component → 4 corners).
      4. **Mockups** — author `scene.html` (11 modules from the brand's assets + approved copy), then
         `render_art.py` → `art/art_std_01..11.png` (Playwright bare mode: `body.bare` hides the CSS
         room and the frame fills the viewport; `device_scale_factor=2`).
      5. **Composite** — `composite.py`: perspective-warp each poster into the quad, **multiply the
         plate's interior luma back onto it** (`shadow_strength`), glass sheen → `frames_v2/*`.
      6. **Sequence** — write `concat.txt` (frames + `duration` lines per `config.beats[].seconds`,
         repeat the last frame line), then `build_video.sh concat.txt music.mp3 out.mp4 <total_s>`.
      
      ## Adapting `scene.html` per brand
      
      - Keep the 11 module ids (`.a1`..`.a11`, `data-i`), the `#thand` icon symbol pattern, and the
        bare-mode CSS. Swap: product image paths, wordmark/icon, palette CSS vars, and the baked
        copy (approved only). Recreate the brand icon as an SVG `<mask>` if the only source has an
        occluding element.
      - Beats are opaque full-bleed; **toggle with opacity, not display** (a per-state `display:flex`
        silently overrides a `display:none` toggle).
      
      ## Split (per adding-and-testing-a-video-format.md)
      
      - **Paid** (already generic caps): `create-image-fal` (plate), `create-music-elevenlabs` (bed).
      - **Free renderer** (`render-brand-identity-reveal`): `scene.html`, `render_art.py`,
        `recrop.py`, `measure_frame.py`, `composite.py`, `build_video.sh`. (`gen_plate.py` is a
        convenience wrapper around the paid `create-image-fal` step — not part of the free cap.)
      
    • recrop.py 1.8 KB
      #!/usr/bin/env python3
      """Re-crop the high-res plate tighter (closer camera) so the frame is bigger.
      Detects the poster interior in the original, then crops a 9:16 window that puts the
      interior at a target width fraction, keeping wall above and the plant in the corner."""
      import sys, numpy as np
      from PIL import Image
      from scipy import ndimage
      
      SRC = "bg/plate_sage_1.png"
      FRAC = float(sys.argv[1]) if len(sys.argv) > 1 else 0.72   # interior width / output width
      TOP_FRAC = float(sys.argv[2]) if len(sys.argv) > 2 else 0.13  # interior top as frac of output H
      
      im = Image.open(SRC).convert("RGB")
      W0, H0 = im.size
      a = np.asarray(im).astype(np.int32)
      R, G, B = a[..., 0], a[..., 1], a[..., 2]
      mx = a.max(2); mn = a.min(2)
      val = mx; sat = np.where(mx > 0, (mx - mn) / np.maximum(mx, 1), 0)
      mask = (val > 150) & (sat < 0.16) & (np.abs(R - G) < 26) & (np.abs(G - B) < 26)
      lbl, n = ndimage.label(mask)
      sizes = ndimage.sum(np.ones_like(lbl), lbl, range(1, n + 1))
      mask = lbl == (int(np.argmax(sizes)) + 1)
      ys, xs = np.where(mask)
      ix0, iy0, ix1, iy1 = xs.min(), ys.min(), xs.max(), ys.max()
      iw, ih = ix1 - ix0, iy1 - iy0
      icx = (ix0 + ix1) / 2
      print("interior in original:", ix0, iy0, ix1, iy1, "w,h", iw, ih)
      
      # crop width so interior spans FRAC of output width
      CW = iw / FRAC
      CH = CW * 1920 / 1080
      scale = 1080 / CW
      # horizontal: center on interior center
      cx0 = icx - CW / 2
      # vertical: interior top at TOP_FRAC of output
      cy0 = iy0 - (TOP_FRAC * 1920) / scale
      # clamp inside original
      cx0 = max(0, min(cx0, W0 - CW))
      cy0 = max(0, min(cy0, H0 - CH))
      box = (int(round(cx0)), int(round(cy0)), int(round(cx0 + CW)), int(round(cy0 + CH)))
      print("crop box:", box, "-> scale", round(scale, 3))
      crop = im.crop(box).resize((1080, 1920), Image.LANCZOS)
      crop.save("bg/plate_final.png")
      print("saved bg/plate_final.png")
      
    • render_art.py 1.3 KB
      #!/usr/bin/env python3
      """Render each artwork standalone (bare mode) at the frame-interior aspect, 2x for crispness."""
      import os, sys
      from playwright.sync_api import sync_playwright
      
      HERE = os.path.dirname(os.path.abspath(__file__))
      SCENE = f"file://{HERE}/scene.html"
      OUT = os.path.join(HERE, "art"); os.makedirs(OUT, exist_ok=True)
      IW, IH = 740, 1068  # matches original frame-interior design; dsf=2 -> 1480x2136
      
      states = sys.argv[1:] or [str(i) for i in range(1, 11)]
      with sync_playwright() as p:
          b = p.chromium.launch()
          pg = b.new_page(viewport={"width": IW, "height": IH}, device_scale_factor=2)
          pg.goto(SCENE); pg.wait_for_timeout(500)
          pg.evaluate("document.body.classList.add('bare')")
          for i in states:
              js = ("() => { document.querySelectorAll('.art').forEach(e=>e.classList.remove('on'));"
                    "var el=document.querySelector('.art[data-i=\"%s\"]'); if(el)el.classList.add('on');"
                    "return document.querySelector('.art.on')?document.querySelector('.art.on').dataset.i:'NONE'; }") % i
              on = pg.evaluate(js)
              pg.wait_for_timeout(500)
              out = os.path.join(OUT, f"art_std_{int(i):02d}.png")
              pg.screenshot(path=out, clip={"x": 0, "y": 0, "width": IW, "height": IH})
              print(f"wrote {out} (on={on})")
          b.close()
      
    • scene.html 20.4 KB · in bundle
  • tests
    • smoke-test.md 1.1 KB
      # Smoke test — brand-identity-reveal
      
      Fast structural checks (no paid calls).
      
      1. **Recipe is valid + within cap**
         ```bash
         python3 -c "import json; r=json.load(open('one-shot-videos/create-brand-identity-reveal-video-from-refs/recipe.json')); \
         assert r['format']=='brand-identity-reveal'; assert 'render-brand-identity-reveal' in r['atoms']; \
         assert len(json.dumps({'recipe':r}))<65536; print('recipe OK')"
         ```
      2. **Scripts present**: `scene.html`, `render_art.py`, `measure_frame.py`, `recrop.py`,
         `composite.py`, `build_video.sh`, `config.example.json`, `PIPELINE.md` all exist under `scripts/`.
      3. **Renderer runs on the reference** (free, needs python playwright + a plate):
         - `render_art.py` produces 11 `art/art_std_NN.png` at the frame-interior aspect.
         - `measure_frame.py` prints 4 corners + writes `bg/corners.txt`.
         - `composite.py` produces 11 `frames_v2/frame_NN.png` at 1080x1920.
      4. **Assembly**: `build_video.sh` produces an mp4 whose `ffprobe` duration ≈ sum(beats.seconds)
         and whose audio stream is present (music) with no speech (Whisper via `watch`).
      
      PASS = all four; no invented copy in any rendered poster.
      
  • SKILL.md 4.7 KB
    ---
    name: render-brand-identity-reveal
    description: Render a 'brand identity reveal' video from a config — a single poster frame in a real, softly-lit space (real wall, soft-focus plant in the corner, dappled leaf shadow, illuminated poster) whose artwork HARD-CUTS through ~10 on-brand poster mockups (hero product, IG post, hanging banners, sticker sheet, logo lockup, poster, two lifestyle stills, packaging, big icon) then holds on a brand end card. The environment plate is one create-image-fal generation; the mockups are real-DOM HTML frame-stepped via Playwright, perspective-composited into the detected frame quad with the plate's real leaf-shadow multiplied back onto each poster (reads as behind glass), sequenced by FFmpeg. Deterministic assembly, FREE (the plate comes from create-image-fal, the bed from create-music-elevenlabs), music bed only and approved brand copy only. Use for the brand-identity-reveal format.
    status: active
    ---
    
    # render-brand-identity-reveal
    
    Render the 'brand identity reveal' format from a config. The signature is a single fixed
    poster frame in a REAL, softly-lit space (the illuminated poster-frame look of a
    boutique/cinema): a real wall, a real soft-focus plant in the lower-left corner, dappled
    leaf shadow. The artwork INSIDE the frame HARD-CUTS (no crossfade) through 11 beats — 10
    on-brand poster mockups + a brand END CARD held ~3s. Music bed only, NO voiceover; copy is
    baked into each mockup, never overlaid as captions.
    
    This capability is the **FREE** assembly only. The paid parts are separate generic
    capabilities the recipe names — `create-image-fal` (the one-shot environment plate) and
    `create-music-elevenlabs` (the bed). Never re-implement them here.
    
    ## Three layers
    
    1. **Environment plate** (PAID, `create-image-fal`, flux-pro ultra, 9:16) — an *empty* lit
       poster frame in a real space, high-res so the camera can be pushed closer by cropping.
       Pick a wall color that makes the brand's poster colors POP (complement of the dominant hue).
    2. **Mockups** (FREE) — real-DOM HTML/CSS (`scene.html`), one poster per beat, built from the
       brand's real assets + approved copy, frame-stepped via Playwright (`render_art.py`, bare
       mode, device_scale_factor 2).
    3. **Composite** (FREE) — `measure_frame.py` detects the blank poster interior quad;
       `composite.py` perspective-warps each poster into it AND multiplies the plate's real
       leaf-shadow/light back onto the poster (so it reads as behind glass) + a glass sheen;
       `build_video.sh` sequences the frames with the music bed.
    
    ## Inputs
    
    - `config.json` — copy `scripts/config.example.json` and edit (canvas, plate prompt +
      wall_style, camera crop, per-beat durations, end-card copy). Schema + per-file working-dir
      layout are documented in `scripts/PIPELINE.md`.
    - The brand's REAL product/packaging/lifestyle stills, wordmark, and icon (recreate the icon
      as an SVG `<mask>` if the only source has an occluding element). Approved copy only.
    
    ## Workflow (free assembly)
    
    ```bash
    CAP=skills/ads/capabilities/render-brand-identity-reveal
    RUN=<project>/working    # holds scene.html + assets/ + the create-image-fal plate in bg/
    
    python3 $CAP/scripts/recrop.py 0.72 0.13      # camera distance (bigger frac = bigger frame)
    python3 $CAP/scripts/measure_frame.py         # detect the blank poster interior quad
    python3 $CAP/scripts/render_art.py            # render each poster standalone (Playwright, dsf 2)
    python3 $CAP/scripts/composite.py             # warp into frame + shadow multiply + sheen
    bash    $CAP/scripts/build_video.sh $RUN/concat.txt $RUN/music.mp3 out.mp4 13.05 1.0
    ```
    
    Then `watch` the master (music-only, every poster legible, shadow falls across the art, end
    card holds). See `scripts/PIPELINE.md` for adapting `scene.html` per brand.
    
    ## Rules
    
    - Approved brand copy ONLY — never invent claims, customers, results, or testimonials.
    - Toggle beats with OPACITY, not `display` (a per-state `display:flex` silently overrides a
      `display:none` toggle and paints one state over all others).
    - Camera distance = re-crop the high-res plate (`recrop.py`), don't regenerate.
    - The realism trick = MULTIPLY the plate's real shadow/light back onto each composited poster.
    - Music bed only, no VO. Close on the brand end card.
    
    ## Failure Modes
    
    - Every rendered frame identical → a per-beat `display:` rule beat the visibility toggle; use
      opacity.
    - Playwright `evaluate(fn, arg)` didn't switch state in a loop → bake the beat index into the
      JS string and return the applied state to assert it.
    - Composited poster looks pasted-on → you skipped the shadow multiply (shadow_strength ~0.85).
    - Studio product won't cut out (white cap == seamless bg) → present as a photo-tile, don't
      corner-flood-fill.
    - Playwright missing under node → use the python playwright + cached chromium.
    
  • skill.meta.json 333 B
    {
      "slug": "render-brand-identity-reveal",
      "category": "capabilities",
      "domain": "ads",
      "tags": [
        "ads"
      ],
      "installation": {
        "base_command": "npx goose-skills install render-brand-identity-reveal",
        "supports": [
          "claude",
          "cursor",
          "codex"
        ]
      },
      "requires_skills": [
        "watch"
      ]
    }
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related