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
Join clipA.mp4 and clipB.mp4 with a fire transition Colors: ember, red, orange, amber, near white
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.
Put a sale badge on @clip1.mp4, give @clip2.mp4 a VHS look, then join them with a fire transition.
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 |
|---|---|---|
D | 1.5 | Length of the transition in seconds, from the first flame tips to the last. |
FLAME | 0.35 | Flame height above the burning edge, as a share of the frame height. |
RAGGED | 0.04 | How far the burning edge wobbles up and down. |
RISE | 1.2 | How fast the flames lick upwards, in frame heights per second. |
COLORS | ember, red, orange, amber, near white | Flame colors from the tips to the edge. |
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
Join clipA.mp4 and clipB.mp4 with a 1.5 second fire transition starting 1.5 seconds before the end of A, so the output is 10.5 seconds long. A horizontal burning edge rises from below the frame to above it at a steady speed: its height runs from 1.35 frame heights down to -0.35 (0 is the top, 1 the bottom) over the 1.5 seconds, wobbling up and down by 0.04 frame heights with smooth noise across the width. Below the edge show B, above it show A. On top of the edge draw flames up to 0.35 frame heights tall: smooth random noise that scrolls upward at 1.2 frame heights per second sets each tongue's height between 30% and 120% of that, colored from near white (255, 245, 200) and amber (255, 180, 40) at the edge through orange (250, 100, 10) and red (180, 25, 0) to dark ember (40, 5, 0) at the tips, opaque except the top 30% of each tongue, with a 0.01 frame height white-hot line right on the edge. Darken A to 15% in the 0.03 frame heights just above the edge. Crossfade the audio over the same 1.5 seconds. Match B's size to A, output 30fps H.264 yuv420p. Use Python with numpy to draw the frames and ffmpeg to read and write the video, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as fire-transition.py and run:
python3 fire-transition.py clipA.mp4 clipB.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Fire transition: a wall of flames rises from the bottom of the frame and burns clip A away to reveal clip B.
Usage: python3 fire-transition.py clipA.mp4 clipB.mp4 output.mp4 (both clips need an audio track)
Needs ffmpeg and numpy (pip install numpy). The flames are drawn from random noise, so no stock fire clip is needed.
"""
import json
import subprocess
import sys
import numpy as np
D = 1.5 # transition length in seconds, from the first flame tips to the last
FLAME = 0.35 # flame height above the burning edge, as a share of the frame height
RAGGED = 0.04 # how far the burning edge wobbles up and down
CHAR = 0.03 # height of the scorched band just above the edge
RISE = 1.2 # how fast the flame texture climbs, in frame heights per second
HEAT = [0, 0.25, 0.5, 0.75, 1.0] # flame heat...
COLORS = [(40, 5, 0), (180, 25, 0), (250, 100, 10), (255, 180, 40), (255, 245, 200)] # ...and its color: ember, red, orange, amber, near white
SEED = 3 # change for different flames
def noise(x, y, cols, rows, layer):
"""Smooth random values in 0-1 at x, y (0-1 across and down; y may run past 1), on a cols x rows grid."""
grid = np.random.default_rng([SEED, layer]).random((64, cols + 1))
gx, gy = x * cols, y * rows
ix, iy = np.floor(gx).astype(int), np.floor(gy).astype(int)
fx, fy = gx - ix, gy - iy
fx, fy = fx * fx * (3 - 2 * fx), fy * fy * (3 - 2 * fy)
iy0, iy1, ix1 = iy % 64, (iy + 1) % 64, np.minimum(ix + 1, cols)
top = grid[iy0, ix] * (1 - fx) + grid[iy0, ix1] * fx
bottom = grid[iy1, ix] * (1 - fx) + grid[iy1, ix1] * fx
return top * (1 - fy) + bottom * fy
def burn(frame_a, frame_b, t):
"""Clip A burning away to clip B, u = 0 -> 1 through the transition."""
u = (t - offset) / D
edge = 1 + FLAME - (1 + 2 * FLAME) * u + RAGGED * (noise(xs[0], t * 0.8, 9, 1, 0) - 0.5)
d = edge[None, :] - ys # distance above the burning edge (negative: already burned)
picture = np.where((d < 0)[..., None], frame_b, frame_a * np.clip(d / CHAR, 0.15, 1)[..., None])
climb = ys + RISE * t # the texture scrolls up, so the flames lick upwards
flicker = 0.6 * noise(xs, climb, 9, 5, 1) + 0.4 * noise(xs, climb * 1.7, 22, 10, 2)
reach = FLAME * (0.3 + 0.9 * flicker) # each tongue of flame reaches a different height
heat = np.clip(1 - d / reach, 0, 1) * (d > 0) # hottest at the edge, cooling towards the tips
heat = np.maximum(0.85 * heat, np.exp(-(d / 0.01) ** 2)) # white-hot line right on the edge
color = np.stack([np.interp(heat, HEAT, [c[k] for c in COLORS]) for k in range(3)], axis=2)
alpha = np.clip(heat / 0.3, 0, 1)[..., None] # soft only at the very tips
return picture * (1 - alpha) + color * alpha
def probe(path):
info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
"stream=width,height:format=duration", "-of", "json", path]))
return info["streams"][0]["width"], info["streams"][0]["height"], float(info["format"]["duration"])
def frames(path):
"""Decode a clip at the output's size and frame rate, one RGB array per frame."""
dec = subprocess.Popen(["ffmpeg", "-v", "error", "-i", path, "-vf",
f"scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={fps}",
"-f", "rawvideo", "-pix_fmt", "rgb24", "-"], stdout=subprocess.PIPE)
while raw := dec.stdout.read(w * h * 3):
yield np.frombuffer(raw, np.uint8).reshape(h, w, 3).astype(np.float32)
a, b, out = sys.argv[1:4]
w, h, len_a = probe(a)
fps = 30
offset = len_a - D
ys, xs = np.mgrid[0:h, 0:w] / np.array([h, w]).reshape(2, 1, 1)
enc = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", a, "-i", b,
"-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}", "-r", str(fps), "-i", "-",
"-filter_complex", f"[0:a][1:a]acrossfade=d={D}[au]",
"-map", "2:v", "-map", "[au]", "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "aac", out],
stdin=subprocess.PIPE)
clip_a, clip_b = frames(a), frames(b)
i = 0
for frame_a in clip_a: # A on its own, then A burning into the start of B
t = i / fps
picture = frame_a if t < offset else burn(frame_a, next(clip_b), t)
enc.stdin.write(picture.clip(0, 255).astype(np.uint8).tobytes())
i += 1
for frame_b in clip_b: # the rest of B
enc.stdin.write(frame_b.astype(np.uint8).tobytes())
enc.stdin.close()
sys.exit("ffmpeg failed" if enc.wait() else 0)





