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 fold transition Length of the transition in seconds: the first half folds A, the second unfolds B: 1.0 Output frame rate: 30
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 fold 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.0 | Length of the transition in seconds: the first half folds A, the second unfolds B. |
FPS | 30 | Output frame rate. |
BACKGROUND | (16, 16, 20) | Color behind the sheet. |
PAPER | (246, 243, 236) | Color of the sheet's back, seen while it's folded. |
SHOW_THROUGH | 0.1 | How much of the picture shows through the paper's back (0-1). |
SHRINK | 0.12 | How much smaller the sheet gets mid-fold, as if pulled back. |
LENS | 2.5 | Camera distance in frame heights; smaller exaggerates the perspective. |
SHADOW | 0.4 | How dark the flap's shadow on the lower half gets (0-1). |
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 second paper-fold transition, overlapping the clips by 1 second (the output is 11 seconds, 540x960, 30fps). The transition covers the last 1.0 second of A and the first 1.0 second of B, frame by frame: A plays untouched until 5.0 seconds, and after 6.0 seconds B carries on from 1.0 second in, full frame. On each of the 30 transition frames, with progress p = (i + 0.5) / 30 for frame i, draw the frame as a sheet on a solid RGB (16, 16, 20) backdrop. The sheet is scaled by s = 1 - 0.12 * sin(pi * p) about the frame center, and has a horizontal crease across its middle at y = 480. Its bottom half stays flat (from y = 480 to 480 + 480 * s, centered across) and is darkened by multiplying it by 1 - 0.4 * angle / 180. Its top half is a flap hinged on the crease that turns toward the camera and down over the bottom half by angle degrees: for frames with p < 0.5 the sheet is A and angle = 180 * e(2p); for the rest the sheet is B and angle = 180 * (1 - e(2p - 1)), where e is a cubic ease-in-out (4x^3 below 0.5, else 1 - (2 - 2x)^3 / 2). Put the flap's outer edge at height y = -480 * s * cos(angle) and depth z = -480 * s * sin(angle) relative to the crease (negative z is toward the camera), with the camera 2.5 * 960 = 2400 pixels away: with k = 2400 / (2400 + z), the outer edge's corners are at x = 270 +- 270 * s * k, y = 480 + y * k, and the crease's corners at x = 270 +- 270 * s, y = 480. Below 90 degrees, warp the clip's top half (rows 0 to 479) onto that quad (top edge to the outer edge) with OpenCV's getPerspectiveTransform and warpPerspective, multiplied by 0.6 + 0.4 * cos(angle). From 90 degrees on, the flap shows its back: paper of RGB (246, 243, 236) mixed with 10% of the clip's top half flipped upside down (as if the print shows through), with a soft shadow along the crease (multiplied from 0.85 at the crease to 1.0 over the first 32 rows), warped onto the quad from the crease down to the outer edge and multiplied by 0.8 - 0.2 * cos(angle), so it is pure paper color when it lies flat at 180 degrees. Warp a mask of ones the same way to blend each layer with soft edges. Crossfade the audio over the same 1 second. Use Python with OpenCV and numpy for the frames and ffmpeg for the rest (H.264, yuv420p); there is no text, but if you add any, draw it with Pillow, not ffmpeg's drawtext. Keep the audio and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as fold-transition.py and run:
python3 fold-transition.py clipA.mp4 clipB.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Fold transition: clip A folds in half like a sheet of paper, top over bottom, then clip B unfolds out of the fold.
Usage: python3 fold-transition.py clipA.mp4 clipB.mp4 output.mp4 (both clips need an audio track)
Needs ffmpeg, OpenCV and numpy (pip install opencv-python-headless numpy).
"""
import json
import math
import subprocess
import sys
from itertools import islice
import cv2
import numpy as np
D = 1.0 # transition length in seconds: the first half folds A, the second unfolds B
FPS = 30 # output frame rate
BACKGROUND = (16, 16, 20) # color behind the sheet
PAPER = (246, 243, 236) # color of the sheet's back, seen while it's folded
SHOW_THROUGH = 0.1 # how much of the picture shows through the paper's back (0-1)
SHRINK = 0.12 # how much smaller the sheet gets mid-fold, as if pulled back (0 = not at all)
LENS = 2.5 # camera distance in frame heights: smaller exaggerates the perspective
SHADOW = 0.4 # how dark the flap's shadow on the lower half gets (0-1)
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 as w x h RGB frames at FPS, cropping it to fill the frame if its shape differs."""
ff = 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 True:
data = ff.stdout.read(w * h * 3)
if len(data) < w * h * 3:
return
yield np.frombuffer(data, np.uint8).reshape(h, w, 3)
def ease(p):
"""Slow start, fast middle, slow landing (cubic ease-in-out)."""
return 4 * p ** 3 if p < 0.5 else 1 - (-2 * p + 2) ** 3 / 2
def paste(canvas, img, corners, light=1.0):
"""Warp img onto the four screen corners (top left, top right, bottom right, bottom left) with soft edges."""
ih, iw = img.shape[:2]
m = cv2.getPerspectiveTransform(np.float32([(0, 0), (iw, 0), (iw, ih), (0, ih)]), np.float32(corners))
warped = cv2.warpPerspective(img.astype(np.float32) * light, m, (w, h), flags=cv2.INTER_LINEAR)
mask = cv2.warpPerspective(np.ones((ih, iw), np.float32), m, (w, h), flags=cv2.INTER_LINEAR)[..., None]
canvas *= 1 - mask
canvas += warped * mask
def fold(img, angle, scale):
"""img as a sheet `scale` times its size, centered, with its top half turned `angle` degrees (0-180) about the
horizontal center line, toward the camera and down over the bottom half."""
sw, half = w * scale / 2, h * scale / 2
cx, cy = w / 2, h / 2
a = math.radians(angle)
y, z = -half * math.cos(a), -half * math.sin(a) # the flap's outer edge, relative to the crease
k = LENS * h / (LENS * h + z) # perspective: the edge nears the camera, so it draws bigger
edge = [(cx - sw * k, cy + y * k), (cx + sw * k, cy + y * k)]
crease = [(cx - sw, cy), (cx + sw, cy)]
canvas = np.empty((h, w, 3), np.float32)
canvas[:] = BACKGROUND
paste(canvas, img[h // 2:], [crease[0], crease[1], (cx + sw, cy + half), (cx - sw, cy + half)],
1 - SHADOW * angle / 180) # the bottom half darkens as the flap comes over it
edge_on = abs(y * k) < 1 # the flap is a flat line, with nothing to draw
if angle < 90 and not edge_on: # front of the flap: the picture's top half, turning away
paste(canvas, img[:h // 2], [edge[0], edge[1], crease[1], crease[0]], 0.6 + 0.4 * math.cos(a))
elif not edge_on: # back of the flap: paper, lying over the bottom half
back = paper * (1 - SHOW_THROUGH) + img[h // 2 - 1::-1] * SHOW_THROUGH # the print, faint and mirrored
paste(canvas, back, [crease[0], crease[1], edge[1], edge[0]], 0.8 - 0.2 * math.cos(a))
return np.clip(canvas, 0, 255).astype(np.uint8)
def transition(a, b, p):
"""One transition frame at progress p (0-1): A folds shut over the first half, B unfolds over the second."""
scale = 1 - SHRINK * math.sin(math.pi * p)
if p < 0.5:
return fold(a, 180 * ease(2 * p), scale)
return fold(b, 180 * (1 - ease(2 * p - 1)), scale)
A, B, out = sys.argv[1:4]
w, h, length = probe(A)
paper = np.full((h // 2, w, 3), PAPER, np.float32)
paper[:h // 30] *= np.linspace(0.85, 1, h // 30)[:, None, None] # a soft shadow along the crease
cut_in = round((length - D) * FPS) # first frame of the transition
n = round(D * FPS) # frames in the transition
enc = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
"-r", str(FPS), "-i", "-", "-i", A, "-i", B,
"-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)
clip_a, clip_b = frames(A), frames(B)
for f in islice(clip_a, cut_in):
enc.stdin.write(f.tobytes())
for i, (fa, fb) in enumerate(zip(islice(clip_a, n), islice(clip_b, n))):
enc.stdin.write(transition(fa, fb, (i + 0.5) / n).tobytes())
for f in clip_b:
enc.stdin.write(f.tobytes())
enc.stdin.close()
if enc.wait():
sys.exit("ffmpeg failed")





