Get this effect
Switch to Code in your Claude Desktop App
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/skillsAsk for the effect
Add fireworks to input.mp4 Bursts: 0.8s gold at (0.25, 0.20), 1.4s pink at (0.73, 0.17), 2.0s orange at (0.50, 0.11)
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 fireworks 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.
| Setting | Default | What it does |
|---|---|---|
BURSTS | 0.8s gold at (0.25, 0.20), 1.4s pink at (0.73, 0.17), 2.0s orange at (0.50, 0.11) | When each firework bursts, where (0-1 across and down) and its color. Add or remove rows for more or fewer. |
RISE | 0.4 | Seconds each rocket takes to climb from the bottom edge to its burst. |
SPARKS | 150 | Sparks per burst. |
SPEED | 340 | How fast sparks fly out, in pixels per second. |
DRAG | 2.0 | Air drag: higher stops the sparks sooner and makes the burst smaller. |
GRAVITY | 60 | How fast the sparks fall, in pixels per second squared. |
LIFE | 1.7 | Seconds a spark lasts before it has faded out. |
TRAIL | 14 | Points in each spark's trail. |
GLOW | 3 | Glow radius around each spark, in pixels. |
SEED | 3 | Change for a different scatter of sparks. |
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 fireworks to input.mp4. Three bursts, each preceded by a thin pale gold (255, 217, 140) rocket streak that climbs straight up from the bottom edge over 0.4 seconds, slowing as it rises: a gold (255, 190, 30) burst at 0.8 seconds centered 25% across and 20% down, a pink (255, 40, 150) burst at 1.4 seconds centered 73% across and 17% down, and an orange (255, 90, 20) burst at 2.0 seconds centered 50% across and 11% down. Each burst is 150 sparks flying out in all directions at up to 340 pixels per second, slowed by air drag so they stop about 170 pixels from the center, then sagging down under gravity (60 pixels per second squared). Each spark leaves a short trail (its positions over the last 0.17 seconds, fading toward the tail), has a white-hot core with a 3-pixel colored glow, flashes brightly for the first 0.1 seconds, flickers in its second half and has faded out completely 1.7 seconds after its burst. Lay the sparks over the shot in full color (not a screen blend, so they show on the pale backdrop). Nothing shows before the first rocket at 0.4 seconds and nothing is left after 4 seconds. Draw it with Python and numpy (no text, and don't rely on ffmpeg's drawtext filter), keep the audio, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as fireworks.py and run:
python3 fireworks.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Fireworks: three rockets streak up and burst into gold, pink and orange shells of sparks that droop and fade.
Usage: python3 fireworks.py input.mp4 output.mp4
Needs ffmpeg and numpy (pip install numpy).
"""
import json
import subprocess
import sys
import numpy as np
BURSTS = [ # (second it bursts, x, y as 0-1 across and down the frame, spark color)
(0.8, 0.25, 0.20, (255, 190, 30)), # gold, top left
(1.4, 0.73, 0.17, (255, 40, 150)), # pink, top right
(2.0, 0.50, 0.11, (255, 90, 20)), # orange, top middle
]
RISE = 0.4 # seconds each rocket takes to climb from the bottom edge to its burst
SPARKS = 150 # sparks per burst
SPEED = 340 # how fast sparks fly out, in pixels per second
DRAG = 2.0 # air drag: higher stops the sparks sooner and makes the burst smaller
GRAVITY = 60 # how fast the sparks fall, in pixels per second squared
LIFE = 1.7 # seconds a spark lasts before it has faded out
TRAIL = 14 # points in each spark's trail
GLOW = 3 # glow radius around each spark, in pixels
SEED = 3 # change for a different scatter of sparks
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 blur(img, r):
"""Box blur of radius r along both axes, run twice so it looks like a soft gaussian glow."""
for _ in range(2):
for axis in (0, 1):
c = np.cumsum(np.pad(img, [(r + 1, r) if a == axis else (0, 0) for a in range(3)]), axis=axis)
img = (np.take(c, range(2 * r + 1, c.shape[axis]), axis) - np.take(c, range(c.shape[axis] - 2 * r - 1), axis)) / (2 * r + 1)
return img
def points(t):
"""Every bright point at time t: x, y, brightness and color (0-1), rockets and spark trails together."""
xs, ys, power, colors = [], [], [], []
for (at, bx, by, color), (vx, vy, flicker) in zip(BURSTS, shells):
cx, cy = bx * w, by * h
col = np.array(color, np.float32) / 255
if -RISE < t - at < 0: # the rocket: a pale gold streak, slowing as it nears the top
tail = np.linspace(0, 1, 80)
p = 1 - (np.clip(at - t + 0.08 * tail, 0, RISE) / RISE) ** 2
xs.append(np.full(80, cx)), ys.append(h + (cy - h) * p), power.append(2 * (1 - tail))
colors.append(np.tile((1, 0.85, 0.55), (80, 1)))
for k in range(TRAIL):
age = t - at - k * 0.012
weight = 1 - k / TRAIL
if 0 <= age < LIFE:
spread = (1 - np.exp(-DRAG * age)) / DRAG
fade = (1 - age / LIFE) ** 0.8 * (1 + 1.5 * np.exp(-age / 0.05)) # flash at the burst, then fade
twinkle = np.where(age > LIFE * 0.5, 0.5 + 0.5 * ((flicker + round(t * fps)) % 3 > 0), 1)
xs.append(cx + vx * spread), ys.append(cy + vy * spread + GRAVITY * age ** 2 / 2)
power.append(fade * twinkle * weight), colors.append(np.repeat(col[None], SPARKS, 0))
if not xs:
return None
return np.concatenate(xs), np.concatenate(ys), np.concatenate(power), np.concatenate(colors)
def lights(t):
"""The light layer at time t (0 = none, 1 and up = full color): white-hot spark cores inside a colored glow."""
found = points(t)
if found is None:
return None
x, y, p, c = found
inside = (x >= 1) & (x < w - 1) & (y >= 1) & (y < h - 1)
x, y, p, c = x[inside].astype(int), y[inside].astype(int), p[inside], c[inside]
dots = np.zeros((h, w, 3), np.float32)
hot = (0.5 * c + 0.5) * p[:, None] # cores run toward white
for dx, dy in ((0, 0), (1, 0), (-1, 0), (0, 1), (0, -1)):
np.add.at(dots, (y + dy, x + dx), hot * (1 if dx == dy == 0 else 0.5))
halo = np.zeros((h, w, 3), np.float32)
np.add.at(halo, (y, x), c * p[:, None])
return dots * 2 + blur(halo, GLOW) * 10
src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
rng = np.random.default_rng(SEED)
shells = []
for _ in BURSTS:
angle = rng.uniform(0, 2 * np.pi, SPARKS)
speed = SPEED * np.sin(rng.uniform(0.3, np.pi / 2, SPARKS)) * rng.uniform(0.9, 1.0, SPARKS) # a sphere seen flat
shells.append((speed * np.cos(angle), speed * np.sin(angle), rng.integers(0, 3, SPARKS)))
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):
img = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
light = lights(i / fps)
if light is not None:
alpha = np.minimum(light.max(axis=2, keepdims=True), 1) # laid over the shot, so colors hold on bright backdrops
color = light / np.maximum(light.max(axis=2, keepdims=True), 1e-6)
img = (img * (1 - alpha) + 255 * color * alpha).astype(np.uint8)
writer.stdin.write(img.tobytes())
i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
sys.exit("ffmpeg failed")





