How to add a logo reveal with Claude

The shot dims as a white LUMERA wordmark grows and fades in over the bottle, then a gold glint runs across the letters.

Get this effect

  1. Switch to Code in your Claude Desktop App

  2. Install the skill (once)

    Show the prompt
    Install the Recharm video effects skill: download https://recharm-growth-staging.s3.us-east-2.amazonaws.com/website/downloads/recharm-video-effects.zip and unzip it into ~/.claude/skills
  3. Ask for the effect

    Add a logo reveal to input.mp4
    Logo file: None
    Brand, tagline: LUMERA, SKINCARE

Several videos, several effects, one ask

Put your clips in the folder, mention them with @, and ask for everything at once. Claude runs one tested script after another on each clip, then joins them.

Add a logo reveal to @clip1.mp4, put a sale badge on @clip2.mp4, give @clip3.mp4 a VHS look, then join all three with crossfades.

Settings you can change

Ask Claude to change any of these, or edit them at the top of the script.

SettingDefaultWhat it does
LOGO_FILENoneA transparent PNG of your logo; None draws a wordmark from BRAND and TAGLINE.
BRAND, TAGLINELUMERA, SKINCAREThe wordmark and the small line under it.
START1.0Second the reveal begins.
FADE, GROW0.5, 0.8Seconds the logo takes to fade in and to grow to full size.
FROM_SCALE0.5Logo size at the start of the reveal, as a share of its full size.
DIM0.65How much darker the footage gets behind the logo (0-1).
LOGO_W0.62Logo width as a share of the frame width.
SWEEP_START, SWEEP2.0, 0.6Second the glint starts and how long it takes to cross the logo.
GLINT(255, 200, 80)Glint color.

Without the skill

Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.

Show the prompt
Add a logo reveal to input.mp4. Draw the logo with Python and Pillow (don't rely on ffmpeg's drawtext filter) on a transparent 400x300 canvas, all in white Arial Bold: a ring 90 pixels across with a 6 pixel outline at the top center, the letter "L" 50 pixels tall centered in it, "LUMERA" 62 pixels tall with 10 pixels between letters starting 118 pixels down, a 100x4 pixel rule at 214 pixels down, and "SKINCARE" 22 pixels tall with 9 pixels between letters at 236 pixels down, everything centered across. Crop the canvas to its content and scale it to 62% of the frame width (335 pixels), centered in the frame. From 1.0 seconds, fade the logo in over 0.5 seconds while the footage behind it darkens to 35% of its brightness over the same time, and grow the logo from 50% to full size over 0.8 seconds, both easing out (cubic). Then, from 2.0 to 2.6 seconds, sweep a gold (255, 200, 80) glint across the logo only, left to right: a band tilted 20 degrees from vertical with its top leaning right, a Gaussian falloff with a 28 pixel half-width, starting and ending just off the logo. Leave everything before 1.0 seconds untouched, keep the logo and dimming to the end, keep the audio, and save it as output.mp4.

Run it without Claude

This is the exact script we tested. Save it as logo-reveal.py and run:

python3 logo-reveal.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Logo reveal: the footage dims, a white logo grows and fades in at the center, then a gold glint sweeps across it.

Usage: python3 logo-reveal.py input.mp4 output.mp4
Needs ffmpeg, Pillow and numpy (pip install pillow numpy).
"""
import json
import subprocess
import sys
from pathlib import Path

import numpy as np
from PIL import Image, ImageDraw, ImageFont

LOGO_FILE = None          # path to a transparent PNG of your logo; None draws a wordmark from BRAND and TAGLINE
BRAND = "LUMERA"          # wordmark
TAGLINE = "SKINCARE"      # small line under the wordmark
START = 1.0               # second the reveal begins
FADE = 0.5                # seconds the logo takes to fade in (the footage dims over the same time)
GROW = 0.8                # seconds the logo takes to grow to full size
FROM_SCALE = 0.5          # logo size at the start of the reveal, as a share of its full size
DIM = 0.65                # how much darker the footage gets behind the logo (0-1)
LOGO_W = 0.62             # logo width as a share of the frame width, centered
SWEEP_START = 2.0         # second the glint enters the logo's left edge
SWEEP = 0.6               # seconds the glint takes to cross the logo
GLINT = (255, 200, 80)    # glint color
GLINT_W = 28              # half-width of the glint, in logo pixels
GLINT_TILT = 20           # glint tilt from vertical, in degrees (top leans right, like /)
FONTS = ["/System/Library/Fonts/Supplemental/Arial Bold.ttf",     # macOS
         "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",  # Linux
         "C:/Windows/Fonts/arialbd.ttf"]                          # Windows
FONT_FILE = next((f for f in FONTS if Path(f).exists()), None) or sys.exit("No bold font found: add one to FONTS")


def ease_out(p):
    p = min(max(p, 0), 1)
    return 1 - (1 - p) ** 3


def spaced(draw, text, font, center_x, y, tracking):
    """Draw text with extra space between the letters, centered on center_x."""
    widths = [draw.textlength(ch, font=font) for ch in text]
    x = center_x - (sum(widths) + tracking * (len(text) - 1)) / 2
    for ch, cw in zip(text, widths):
        draw.text((x, y), ch, font=font, fill="white")
        x += cw + tracking


def wordmark():
    """A white ring with the brand's first letter, the brand name and a tagline under a thin rule."""
    img = Image.new("RGBA", (400, 300))
    draw = ImageDraw.Draw(img)
    draw.ellipse([155, 0, 245, 90], outline="white", width=6)
    draw.text((200, 46), BRAND[0], font=ImageFont.truetype(FONT_FILE, 50), anchor="mm", fill="white")
    spaced(draw, BRAND, ImageFont.truetype(FONT_FILE, 62), 200, 118, 10)
    draw.rectangle([150, 214, 250, 217], fill="white")
    spaced(draw, TAGLINE, ImageFont.truetype(FONT_FILE, 22), 200, 236, 9)
    return img.crop(img.getbbox())


def glint(logo, t):
    """The logo with a tilted gold band crossing it at time t (the band starts and ends just off the logo)."""
    p = (t - SWEEP_START) / SWEEP
    if not 0 <= p <= 1:
        return logo
    lh, lw = logo.shape[:2]
    y, x = np.mgrid[0:lh, 0:lw].astype(np.float32)
    slope = np.tan(np.radians(GLINT_TILT))
    along = x + (lh - y) * slope
    center = -2 * GLINT_W + p * (lw + lh * slope + 4 * GLINT_W)
    g = np.exp(-((along - center) / GLINT_W) ** 2)[..., None]
    out = logo.copy()
    out[..., :3] = logo[..., :3] * (1 - g) + np.array(GLINT, np.float32) * g
    return out


def frame(img, t):
    """One output frame: the footage dimmed and the logo on top, at its size and opacity for time t."""
    fade = ease_out((t - START) / FADE) if t >= START else 0
    if fade == 0:
        return img
    out = img.astype(np.float32) * (1 - DIM * fade)
    scale = FROM_SCALE + (1 - FROM_SCALE) * ease_out((t - START) / GROW)
    layer = Image.fromarray(glint(logo, t).astype(np.uint8))
    layer = np.asarray(layer.resize((round(layer.width * scale), round(layer.height * scale)), Image.LANCZOS), np.float32)
    lh, lw = layer.shape[:2]
    x0, y0 = (w - lw) // 2, (h - lh) // 2
    a = layer[..., 3:] / 255 * fade
    region = out[y0:y0 + lh, x0:x0 + lw]
    out[y0:y0 + lh, x0:x0 + lw] = region * (1 - a) + layer[..., :3] * a
    return out.astype(np.uint8)


def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height,r_frame_rate", "-of", "json", path]))
    s = info["streams"][0]
    num, den = s["r_frame_rate"].split("/")
    return s["width"], s["height"], s["r_frame_rate"], float(num) / float(den)


src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
art = Image.open(LOGO_FILE).convert("RGBA") if LOGO_FILE else wordmark()
art = art.resize((round(w * LOGO_W), round(w * LOGO_W * art.height / art.width)), Image.LANCZOS)
logo = np.asarray(art, np.float32)

reader = subprocess.Popen(["ffmpeg", "-v", "error", "-i", src, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
                          stdout=subprocess.PIPE)
writer = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
                           "-r", rate, "-i", "-", "-i", src, "-map", "0:v", "-map", "1:a?",
                           "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
                          stdin=subprocess.PIPE)
i = 0
while raw := reader.stdout.read(w * h * 3):
    writer.stdin.write(frame(np.frombuffer(raw, np.uint8).reshape(h, w, 3), i / fps).tobytes())
    i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
    sys.exit("ffmpeg failed")

Find 3x winning ads

Recharm cuts your ad footage into tagged, searchable clips: hooks, testimonials, product shots. Find the shots that win, add the effect with Claude, and ship 3x more ad edits.

Start 14-day free trial