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 cloud transition Length of the transition in seconds; the cut to B happens unseen at the midpoint: 2.0 Share of the transition the frame stays fully covered around the cut: 0.1
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 cloud 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 | 2.0 | Length of the transition in seconds; the cut to B happens unseen at the midpoint. |
HOLD | 0.1 | Share of the transition the frame stays fully covered around the cut. |
LIGHT | (248, 249, 251) | Sunlit cloud color. |
SHADOW | (206, 212, 222) | Cloud color in the shadowed folds. |
RISE | 900 | How fast the clouds stream down past the camera, in pixels per second. |
CLIMB | 0.18 | How far clip A sinks as the camera rises, as a share of the frame height. |
PUSH | 1.25 | Zoom on A as it disappears and on B as it first shows; B settles back to 1. |
BLOB | 150 | Rough size of the cloud puffs, in pixels. |
SOFT | 0.14 | Width of the feathered cloud edge (0 = hard 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 (both 540x960, 6 seconds) with a 2 second cloud transition, using Python with numpy and ffmpeg to read and write the frames. Make the clouds from soft cloudy noise (random noise low-pass filtered so the puffs are about 150 pixels across) that streams downward at 900 pixels per second and swells to 1.6 times its size over the transition, so it feels like flying up through it; shade it from sunlit (248, 249, 251) to a cool gray (206, 212, 222) in the folds, with a feathered edge. The transition runs from 4.0 to 6.0 seconds of the output. From 4.0 to 4.9 seconds the cloud bank comes down from the top edge and fills the frame top first while clip A zooms in around its center from 1.0 to 1.25 and slides down by 18% of the frame height (smoothstep easing), as if the camera were rising. The frame is fully covered from 4.9 to 5.1 seconds and the cut from A to B happens hidden behind it at 5.0 seconds. From 5.1 to 6.0 seconds a hole opens in the middle of the frame and widens out to the corners, while clip B settles from 1.25 zoom back to 1.0 (smoothstep), so at 5.45 seconds the center of B is clear and the top and bottom edges are still cloud. B then plays to its end, for a 10 second video at 30fps. Crossfade the audio over the same 2 seconds, don't use ffmpeg's drawtext filter, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as cloud-transition.py and run:
python3 cloud-transition.py clipA.mp4 clipB.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Cloud transition: the camera climbs out of clip A into a white cloud bank, then the clouds part from the middle to reveal clip B.
Usage: python3 cloud-transition.py clipA.mp4 clipB.mp4 output.mp4 (both clips need an audio track)
Needs ffmpeg and numpy (pip install numpy).
"""
import json
import subprocess
import sys
import numpy as np
D = 2.0 # transition length in seconds; the cut to B happens unseen at the midpoint
HOLD = 0.1 # share of D the frame stays fully white around the cut
LIGHT = (248, 249, 251) # sunlit cloud color
SHADOW = (206, 212, 222) # cloud color in the shadowed folds, a cool gray
RISE = 900 # how fast the clouds stream down past the camera, in pixels per second
CLIMB = 0.18 # how far clip A sinks as the camera rises, as a share of the frame height
PUSH = 1.25 # zoom at the moment A disappears, and B's zoom when it first shows (settles to 1)
BLOB = 150 # rough size of the cloud puffs, in pixels
SOFT = 0.14 # width of the feathered cloud edge (0 = hard edge)
SEED = 7 # change for a different cloud pattern
FPS = 30
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):
"""Every frame of a clip, fitted to A's size at 30fps, as a float RGB array."""
fit = f"scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={FPS}"
ff = subprocess.Popen(["ffmpeg", "-v", "error", "-i", path, "-vf", fit, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
stdout=subprocess.PIPE)
while chunk := ff.stdout.read(w * h * 3):
yield np.frombuffer(chunk, np.uint8).reshape(h, w, 3).astype(np.float32)
def clouds(rng, height, width, size):
"""Soft cloudy noise (0-1) that wraps at the edges: random noise with its fine detail filtered out."""
fy = np.fft.fftfreq(height)[:, None]
fx = np.fft.fftfreq(width)[None, :]
f = np.hypot(fx, fy) * size
field = np.fft.ifft2(np.fft.fft2(rng.standard_normal((height, width))) * np.exp(-f ** 2) / np.maximum(f, 0.3)).real
return ((field - field.min()) / (field.max() - field.min())).astype(np.float32)
def camera(img, zoom, drop):
"""The frame scaled up by zoom around its center and moved down by drop pixels (the camera rising past it)."""
rows = np.clip(((ys - h / 2 - drop) / zoom + h / 2).astype(int), 0, h - 1)
cols = np.clip(((xs - w / 2) / zoom + w / 2).astype(int), 0, w - 1)
return img[rows][:, cols]
def ease(p):
return p * p * (3 - 2 * p)
def cloud_layer(p):
"""Cloud density (0-1) and shading at progress p: the puffs stream downward and swell as the camera flies in."""
swell = 1 + 0.6 * p
rows = ((ys - h / 2) / swell + h / 2 - RISE * p * D).astype(int) % puffs.shape[0]
cols = ((xs - w / 2) / swell + w / 2).astype(int) % puffs.shape[1]
return puffs[rows][:, cols], folds[rows][:, cols]
def frame_at(a, b, p):
"""Frame at progress p (0-1): clouds roll in from the top over A, hold, then part from the center over B."""
density, shade = cloud_layer(p)
if p < 0.5:
amount = min(p / (0.5 - HOLD / 2), 1)
k = ease(amount)
base = camera(a, 1 + (PUSH - 1) * k, CLIMB * h * k)
threshold = 0.5 * density + 0.5 * y # the cloud bank comes down from above
else:
amount = min((1 - p) / (0.5 - HOLD / 2), 1)
base = camera(b, 1 + (PUSH - 1) * ease(amount), 0)
threshold = 0.5 * density + 0.5 * (1 - radius) # a hole opens in the middle and widens
alpha = np.clip((amount * (1 + SOFT) - threshold) / SOFT, 0, 1)[..., None]
color = light + (shadow - light) * shade[..., None]
return base * (1 - alpha) + color * alpha
a_path, b_path, out = sys.argv[1:4]
w, h, a_len = probe(a_path)
rng = np.random.default_rng(SEED)
puffs = clouds(rng, 2 * h, w, BLOB)
folds = clouds(rng, 2 * h, w, BLOB / 2) ** 2
light, shadow = np.array(LIGHT, np.float32), np.array(SHADOW, np.float32)
ys, xs = np.arange(h, dtype=np.float32), np.arange(w, dtype=np.float32)
y = np.linspace(0, 1, h, dtype=np.float32)[:, None] * np.ones((1, w), np.float32)
radius = np.hypot((xs[None, :] - w / 2) / (w / 2), (ys[:, None] - h / 2) / (h / 2)) / np.sqrt(2)
start = round((a_len - D) * FPS) # first frame of the transition
steps = round(D * FPS)
ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
"-r", str(FPS), "-i", "-", "-i", a_path, "-i", b_path,
"-filter_complex", f"[1:a][2:a]acrossfade=d={D}[au]", "-map", "0:v", "-map", "[au]",
"-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "aac", out], stdin=subprocess.PIPE)
b_frames = frames(b_path)
for i, a in enumerate(frames(a_path)):
if i >= start:
a = frame_at(a, next(b_frames), (i - start + 0.5) / steps)
ff.stdin.write(np.clip(a, 0, 255).astype(np.uint8).tobytes())
for b in b_frames:
ff.stdin.write(b.astype(np.uint8).tobytes())
ff.stdin.close()
if ff.wait():
sys.exit("ffmpeg failed")





