How to add spotlight focus with Claude

At one second the scene drops into near darkness while a soft-edged circle of light shrinks onto the bottle and stays there.

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 spotlight focus to input.mp4
    Second the spotlight starts closing in: 1.0
    Seconds it takes to settle: 0.6

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 spotlight focus 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
START1.0Second the spotlight starts closing in.
CLOSE0.6Seconds it takes to settle.
X, Y0.5, 0.52Circle center, as shares of the frame width and height.
RADIUS0.42Final circle radius as a share of the frame width.
FEATHER0.08Width of the soft edge as a share of the frame width.
DIM0.22Brightness kept outside the circle (0 is black, 1 is untouched).

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 spotlight to input.mp4 with Python and numpy, using ffmpeg to read and write the frames. Before 1.0 seconds leave the video untouched. From 1.0 to 1.6 seconds (ease-out), a circle centered at 50% across and 52% down closes in from a radius of 105% of the frame width to 42% of the frame width (227 px on a 540-wide frame), while everything outside it fades to 22% of its brightness. Give the circle a soft edge 8% of the frame width wide; inside it the picture keeps its full brightness. Hold the spotlight to the end. There's no text to draw, but if you add any, draw it with Pillow, not ffmpeg's drawtext. Keep the audio and save it as output.mp4.

Run it without Claude

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

python3 spotlight-focus.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Spotlight focus: the frame goes dark except a soft-edged bright circle that closes in on the product.

Usage: python3 spotlight-focus.py input.mp4 output.mp4
Needs ffmpeg and numpy (pip install numpy).
"""
import json
import subprocess
import sys

import numpy as np

START = 1.0          # second the spotlight starts closing in
CLOSE = 0.6          # seconds it takes to settle
X, Y = 0.5, 0.52     # circle center (0-1 across and down the frame)
RADIUS = 0.42        # final circle radius as a share of the frame width
FEATHER = 0.08       # soft edge width as a share of the frame width
DIM = 0.22           # brightness kept outside the circle (0 = black, 1 = untouched)


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


def light(t):
    """Per-pixel brightness multiplier at time t: 1 inside the circle, DIM outside."""
    p = ease_out((t - START) / CLOSE) if t >= START else 0
    if p == 0:
        return None
    radius = w * RADIUS * (2.5 - 1.5 * p)          # starts wide and closes in on the product
    inside = np.clip((radius - dist) / (w * FEATHER) + 0.5, 0, 1)
    dim = 1 - (1 - DIM) * p                        # the dark fades in as the circle closes
    return (dim + (1 - dim) * inside)[:, :, None]


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)
yy, xx = np.mgrid[0:h, 0:w]
dist = np.hypot(xx - w * X, yy - h * Y)

reader = subprocess.Popen(["ffmpeg", "-v", "error", "-i", src, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
                          stdout=subprocess.PIPE)
writer = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", src,
                           "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
                           "-map", "1:v", "-map", "0:a?", "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p",
                           "-c:a", "copy", "-shortest", out], stdin=subprocess.PIPE)
i = 0
while True:
    raw = reader.stdout.read(w * h * 3)
    if len(raw) < w * h * 3:
        break
    img = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
    mask = light(i / fps)
    writer.stdin.write(img.tobytes() if mask is None else (img * mask).astype(np.uint8).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