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 an explosion to input.mp4 Palette: dark smoke, deep red, orange, yellow, white-hot
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 an explosion 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 |
|---|---|---|
AT | 1.0 | Second it goes off. |
X, Y | 0.5, 0.62 | Where it goes off (0-1 across and down the frame). |
SIZE | 0.40 | Fireball radius at full size, as a share of the frame width. |
GROW | 0.25 | Seconds the fireball takes to reach about two thirds of its size. |
BURN | 1.0 | Seconds of fire before only smoke is left. |
SMOKE | 1.6 | Seconds the smoke then takes to fade away. |
RISE | 70 | How fast the cloud drifts up, in pixels per second. |
FLASH | 0.15 | Seconds the white flash takes to die down. |
SPARKS | 90 | Burning sparks thrown out. |
SPARK_SPEED | 900 | How fast the fastest sparks fly out, in pixels per second. |
PALETTE | dark smoke, deep red, orange, yellow, white-hot | Fireball colors from cold to hot. |
DETAIL | 2 | How much coarser than the frame the cloud is worked out; 1 is sharpest and slowest. |
SEED | 4 | Change for a different cloud and spark pattern. |
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 an explosion to input.mp4 at 1.0 seconds, centered 50% across and 62% down. Before 1.0s the video is unchanged. At 1.0s the whole frame flashes toward white (blend 90% of the way to white, decaying as exp(-age/0.15 s)). A fireball grows from that point: its radius is 0.40 x frame width x (1 - exp(-age/0.25) + 0.12 x age), its center drifts up at 70 pixels per second, and its edge is ragged and billowing (use smooth tileable noise that scrolls over time and shade the colors by it, 0.7 to 1.3). Color it by heat from a palette: white-hot (255, 250, 225) in the core, yellow (255, 200, 60), orange (250, 105, 10), deep red (150, 25, 5) and dark smoke (45, 40, 38) at zero heat. The heat drops steadily so after 1.0 second of burning only smoke is left, and the smoke fades out linearly over the next 1.6 seconds, so nothing is left by 3.6s. Also throw 90 small yellow-white sparks outward from the center at 270 to 900 pixels per second with drag and a little gravity, each with a short fading trail, gone within 1.1 seconds. Lay the fireball and smoke over the shot with real opacity (not a screen blend) so they show on the pale backdrop. Use Python with 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 explosion.py and run:
python3 explosion.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Explosion: a white flash, then a billowing fireball bursts out of one point with flying sparks and cools into rising smoke.
Usage: python3 explosion.py input.mp4 output.mp4
Needs ffmpeg and numpy (pip install numpy).
"""
import json
import subprocess
import sys
import numpy as np
AT = 1.0 # second it goes off
X, Y = 0.5, 0.62 # where it goes off (0-1 across and down the frame)
SIZE = 0.40 # fireball radius at full size, as a share of the frame width
GROW = 0.25 # seconds the fireball takes to reach about two thirds of its size
BURN = 1.0 # seconds of fire before only smoke is left
SMOKE = 1.6 # seconds the smoke then takes to fade away
RISE = 70 # how fast the cloud drifts up, in pixels per second
FLASH = 0.15 # seconds the white flash takes to die down
SPARKS = 90 # burning sparks thrown out
SPARK_SPEED = 900 # how fast sparks fly out, in pixels per second
PALETTE = [(0.00, (45, 40, 38)), # heat 0: dark smoke
(0.30, (150, 25, 5)), # deep red
(0.55, (250, 105, 10)), # orange
(0.80, (255, 200, 60)), # yellow
(1.00, (255, 250, 225))] # white-hot core
DETAIL = 2 # the cloud is worked out on a grid this many times coarser than the frame, then smoothed up
SEED = 4 # change for a different cloud and spark pattern
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 turbulence(size):
"""Smooth random pattern (0-1) that wraps round at every edge: the billows of the cloud."""
f = np.hypot(np.fft.fftfreq(size)[:, None], np.fft.rfftfreq(size)[None, :])
f[0, 0] = 1
tex = np.fft.irfft2(np.fft.rfft2(rng.standard_normal((size, size))) / f ** 1.5 * (f > 0.01), (size, size))
lo, hi = np.percentile(tex, [1, 99])
return np.clip((tex - lo) / (hi - lo), 0, 1)
def sample(tex, u, v):
"""Tex at fractional, wrapping coordinates u (across) and v (down), blended between the four nearest texels."""
n = len(tex)
u0, v0 = np.floor(u).astype(int), np.floor(v).astype(int)
fu, fv = u - u0, v - v0
u0, v0, u1, v1 = u0 % n, v0 % n, (u0 + 1) % n, (v0 + 1) % n
return (tex[v0, u0] * (1 - fu) + tex[v0, u1] * fu) * (1 - fv) + (tex[v1, u0] * (1 - fu) + tex[v1, u1] * fu) * fv
def cloud(age):
"""Fireball color and opacity (0-1) over the whole frame, `age` seconds after it went off."""
radius = SIZE * w * (1 - np.exp(-age / GROW) + 0.12 * age) # bursts out fast, then keeps swelling slowly
cx, cy = X * w, Y * h - RISE * age
u, v = (gx - cx) / radius, (gy - cy) / radius # the frame in fireball radii from its center
boil = 0.6 * sample(big, 70 * u + 9 * age, 70 * v - 14 * age) + 0.4 * sample(small, 150 * u - 6 * age, 150 * v - 20 * age)
density = np.clip(1.7 * (1 - np.hypot(u, v)) + 1.3 * (boil - 0.5), 0, 1) # ragged, billowing edge
heat = np.clip(1.3 * density + 0.8 * (boil - 0.5) + 0.3 - 1.4 * age / BURN, 0, 1) * (density > 0) # cools from the edge in
shade = 0.7 + 0.6 * boil # lit and shadowed billows
fade = 1 - np.clip((age - BURN) / SMOKE, 0, 1)
alpha = np.clip(2 * density, 0, 1) * np.maximum(0.85, heat) * fade
color = [up_rows @ (np.interp(heat, stops, channel) * shade) @ up_cols for channel in colors]
return np.stack(color, axis=-1), (up_rows @ alpha @ up_cols)[..., None]
def sparks(age):
"""Burning sparks as an additive light layer: short trails flying out of the blast, slowing and dropping."""
light = np.zeros((h, w, 3), np.float32)
for k in range(8): # 8 points down each trail, fading to the tail
a = age - k * 0.008
if a <= 0:
continue
travel = (1 - np.exp(-2.5 * a)) / 2.5
x = (X * w + vx * travel).astype(int)
y = (Y * h + vy * travel + 250 * a ** 2).astype(int)
glow = np.clip(1 - a / life, 0, 1)[:, None] * (1 - k / 8) * np.array((1, 0.75, 0.35), np.float32)
inside = (x > 0) & (x < w - 1) & (y > 0) & (y < h - 1)
for dx, dy in ((0, 0), (1, 0), (-1, 0), (0, 1), (0, -1)):
np.add.at(light, (y[inside] + dy, x[inside] + dx), glow[inside] * 255)
return light
def stretch_matrix(n, size):
"""Matrix that resizes n samples to `size` with linear interpolation."""
pos = np.clip((np.arange(size) + 0.5) / size * n - 0.5, 0, n - 1)
m = np.zeros((size, n), np.float32)
i0 = np.minimum(pos.astype(int), n - 2)
m[np.arange(size), i0] = 1 - (pos - i0)
m[np.arange(size), i0 + 1] = pos - i0
return m
src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
rng = np.random.default_rng(SEED)
big, small = turbulence(256), turbulence(256)
gh, gw = -(-h // DETAIL), -(-w // DETAIL)
gy, gx = np.mgrid[0:gh, 0:gw].astype(np.float32) * DETAIL
up_rows, up_cols = stretch_matrix(gh, h), stretch_matrix(gw, w).T
stops = [s for s, _ in PALETTE]
colors = np.array([c for _, c in PALETTE], np.float32).T
angle = rng.uniform(0, 2 * np.pi, SPARKS)
speed = SPARK_SPEED * rng.uniform(0.3, 1.0, SPARKS)
vx, vy = speed * np.cos(angle), speed * np.sin(angle)
life = rng.uniform(0.5, 1.1, 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).astype(np.float32)
age = i / fps - AT
if 0 < age < BURN + SMOKE:
flash = np.exp(-age / FLASH) # the whole frame blows out, then settles
img += (255 - img) * 0.9 * flash
color, alpha = cloud(age)
img = img * (1 - alpha) + color * alpha + sparks(age)
writer.stdin.write(np.clip(img, 0, 255).astype(np.uint8).tobytes())
i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
sys.exit("ffmpeg failed")





