How to add sparkles with Claude

Gold four-pointed star glints with white-hot centers pop up one after another in a ring around the bottle, each growing, turning an eighth of a turn and shrinking away in just over a second.

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 sparkles to input.mp4
    Second the first sparkle appears, and second the last one has faded: 0.5, 5.6
    Number of sparkles over the whole clip: 70

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 sparkles 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
START, END0.5, 5.6Second the first sparkle appears, and second the last one has faded.
COUNT70Number of sparkles over the whole clip.
LIFE1.2Seconds each sparkle takes to grow, spin and fade.
SIZE(32, 80)Smallest and largest ray length from the center, in pixels.
CENTER, RING(0.5, 0.45), (0.22, 0.42, 0.34)Middle of the ring the sparkles appear in, and its inner radius, outer radius across and outer radius down, as shares of the frame.
GOLD, WHITE(255, 196, 70), (255, 255, 255)Color of the rays and glow, and of the hot center.

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 sparkles to input.mp4: 70 four-pointed star glints that appear one after another around the subject. Space their start times evenly from 0.5 to 4.4 seconds (a new one about every 0.06 seconds); each lives 1.2 seconds, its size following sin(pi * p) ^ 1.5 where p goes from 0 to 1 over its life, so it grows from nothing, peaks halfway and shrinks away, while it turns 45 degrees clockwise or counterclockwise. At full size each star has four main rays 32 to 80 pixels long from the center (random per star), four diagonal rays 45% as long, rays that taper to a point and are about 7 pixels wide at the center, a soft round glow of half strength out to about a third of the ray length, and a small pure white (255, 255, 255) center; rays and glow are gold (255, 196, 70). Place the stars at random in an elliptical ring centered 50% across and 45% down the 540x960 frame, with an outer radius of 42% of the width across and 34% of the height down and an inner radius about half that, so none land on the middle of the subject. Blend them over the video normally (alpha "over", not screen or add) so the gold shows on a pale background. Leave the frames before 0.5 seconds and after 5.6 seconds untouched. Use Python with numpy to draw the stars and ffmpeg to read and write the video (don't use ffmpeg's drawtext filter for anything), keep the audio, and save it as output.mp4.

Run it without Claude

This is the exact script we tested. Save it as sparkles.py and run:

python3 sparkles.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Sparkles: gold four-point star glints with white-hot centers twinkle on and off around the subject.

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

import numpy as np

START, END = 0.5, 5.6         # first sparkle appears at START; the last one has faded by END
COUNT = 70                    # number of sparkles over the whole clip
LIFE = 1.2                    # seconds each sparkle takes to grow, spin and fade
SIZE = (32, 80)               # smallest and largest ray length from the center, in pixels
CENTER = (0.5, 0.45)          # middle of the ring the sparkles appear in (0-1 across and down the frame)
RING = (0.22, 0.42, 0.34)     # ring's inner radius, outer radius across and outer radius down (shares of the frame)
GOLD = (255, 196, 70)         # color of the rays and glow
WHITE = (255, 255, 255)       # color of the hot center
SEED = 5                      # change for a different arrangement


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)


def star(length, angle):
    """One sparkle as (color, alpha): four long rays, four short diagonal ones, a soft glow and a white center."""
    g = int(length) + 3
    y, x = np.mgrid[-g:g + 1, -g:g + 1].astype(np.float32)
    rays = np.zeros_like(x)
    for k, reach in [(0, 1.0), (1, 0.45)]:  # main cross, then the diagonals at under half the length
        a = angle + k * np.pi / 4
        u, v = x * np.cos(a) + y * np.sin(a), -x * np.sin(a) + y * np.cos(a)
        for along, across in [(u, v), (v, u)]:
            taper = np.clip(1 - np.abs(along) / (length * reach), 0, 1)
            rays = np.maximum(rays, taper ** 2 * np.exp(-(across / (0.9 + 2.6 * taper)) ** 2))
    r2 = (x * x + y * y) / length ** 2
    glow = 0.5 * np.exp(-r2 / 0.05)
    core = np.exp(-r2 / 0.004)
    alpha = np.clip(np.maximum(rays, glow) + core, 0, 1)
    color = alpha[..., None] * GOLD + core[..., None] * (np.array(WHITE) - GOLD)
    return color, alpha[..., None]


def sparkle_layer(t):
    """Premultiplied color and alpha of every sparkle alive at time t, or None when none is."""
    alive = [s for s in sparkles if 0 < t - s["born"] < LIFE]
    if not alive:
        return None
    color = np.zeros((h, w, 3), np.float32)
    alpha = np.zeros((h, w, 1), np.float32)
    for s in alive:
        p = (t - s["born"]) / LIFE
        scale = np.sin(np.pi * p) ** 1.5           # grows from nothing, peaks halfway, shrinks away
        if scale < 0.05:
            continue
        c, a = star(s["size"] * scale, s["angle"] + s["spin"] * p)
        r = a.shape[0] // 2
        cx, cy = round(s["x"]), round(s["y"])
        x0, y0, x1, y1 = max(cx - r, 0), max(cy - r, 0), min(cx + r + 1, w), min(cy + r + 1, h)
        sy, sx = slice(y0 - cy + r, y1 - cy + r), slice(x0 - cx + r, x1 - cx + r)
        color[y0:y1, x0:x1] = color[y0:y1, x0:x1] * (1 - a[sy, sx]) + c[sy, sx]
        alpha[y0:y1, x0:x1] = alpha[y0:y1, x0:x1] * (1 - a[sy, sx]) + a[sy, sx]
    return color, alpha


src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
rng = np.random.default_rng(SEED)
sparkles = []
for born in np.linspace(START, END - LIFE, COUNT):  # a new sparkle every 0.06 seconds or so
    d = rng.uniform(RING[0] / RING[1], 1)        # how far out in the ring, so none land on the middle
    a = rng.uniform(0, 2 * np.pi)
    sparkles.append({"born": born, "x": w * (CENTER[0] + RING[1] * d * np.cos(a)),
                     "y": h * (CENTER[1] + RING[2] * d * np.sin(a)), "size": rng.uniform(*SIZE),
                     "angle": rng.uniform(0, np.pi / 2), "spin": rng.choice([-1, 1]) * np.pi / 4})

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", "16", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
                          stdin=subprocess.PIPE)
i = 0
while raw := reader.stdout.read(w * h * 3):
    img = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
    layer = sparkle_layer(i / fps)
    if layer is not None:
        color, alpha = layer
        img = (img * (1 - alpha) + color).clip(0, 255).astype(np.uint8)
    writer.stdin.write(img.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