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 match cut Seconds of clip A the cut may leave from: (2.0, 4.0) Seconds of clip B the cut may land on: (0.3, 2.0)
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 match cut.
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 |
|---|---|---|
A_WINDOW | (2.0, 4.0) | Seconds of clip A the cut may leave from. |
B_WINDOW | (0.3, 2.0) | Seconds of clip B the cut may land on. |
STEP | 0.1 | Search step in seconds; smaller finds a closer match but takes longer. |
ALIGN | 0.5 | Seconds clip A takes to slide and zoom onto B's framing before the cut. |
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
Make a match cut from clipA.mp4 to clipB.mp4 (both 540x960, 30fps) with Python, OpenCV and ffmpeg. Search clipA's frames from 2.0 to 4.0 seconds and clipB's frames from 0.3 to 2.0 seconds in 0.1 second steps (every 3rd frame). For each pair, match ORB features (cv2.ORB_create(2000), brute-force Hamming matcher with cross-check), fit a scale + rotation + shift taking B's points onto A's with cv2.estimateAffinePartial2D (RANSAC threshold 3), skip pairs where moving A that way would leave empty edges, and keep the pair whose moved A frame has the lowest mean absolute difference from the B frame (on these clips that is A's frame at 2.4 seconds and B's at 1.0 seconds). Play A untouched until 0.5 seconds before that frame, then ease it (smoothstep) from its own framing to the fitted alignment, so on A's last frame the bottle sits exactly where it is in B's frame. Then hard cut to B, untouched, from the matched frame to its end. Cut the audio at the same points with 0.02 second fades. The result is 540x960, 30fps, H.264 yuv420p and about 7.43 seconds long. Keep the audio and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as match-cut.py and run:
python3 match-cut.py clipA.mp4 clipB.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Match cut: OpenCV finds where the product sits the same way in both shots, lines clip A up on clip B and cuts.
Usage: python3 match-cut.py clipA.mp4 clipB.mp4 output.mp4 (same size and frame rate, both with audio)
Needs ffmpeg, OpenCV and numpy (pip install opencv-python-headless numpy).
"""
import subprocess
import sys
import cv2
import numpy as np
A_WINDOW = (2.0, 4.0) # seconds of clip A the cut may leave from
B_WINDOW = (0.3, 2.0) # seconds of clip B the cut may land on
STEP = 0.1 # search step in seconds
ALIGN = 0.5 # seconds clip A takes to slide and zoom onto B's framing before the cut
orb = cv2.ORB_create(2000)
matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
def read_frames(path):
video = cv2.VideoCapture(path)
frames = []
while True:
ok, img = video.read()
if not ok:
return frames, video.get(cv2.CAP_PROP_FPS)
frames.append(img)
def features(img):
return orb.detectAndCompute(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), None)
def alignment(fa, fb):
"""Scale, rotation and shift taking points in B's frame to the same feature in A's (None if no fit)."""
(ka, da), (kb, db) = fa, fb
pairs = matcher.match(db, da)
if len(pairs) < 10:
return None
pb = np.float32([kb[p.queryIdx].pt for p in pairs])
pa = np.float32([ka[p.trainIdx].pt for p in pairs])
m, _ = cv2.estimateAffinePartial2D(pb, pa, ransacReprojThreshold=3)
return m
def covers_frame(m):
"""True if A, moved by m, still fills the whole frame (no empty edges)."""
corners = np.float32([[0, 0, 1], [w, 0, 1], [0, h, 1], [w, h, 1]]) @ m.T
return corners.min() >= 0 and (corners[:, 0] <= w).all() and (corners[:, 1] <= h).all()
def moved(img, m):
"""A's frame resampled so that its point m @ p lands on output pixel p."""
return cv2.warpAffine(img, m, (w, h), flags=cv2.INTER_CUBIC | cv2.WARP_INVERSE_MAP)
def best_cut():
"""The pair of frames (one from each clip) that look most alike once lined up."""
best = None
ib_range = range(round(B_WINDOW[0] * fps), round(B_WINDOW[1] * fps) + 1, round(STEP * fps))
fb = {ib: features(b[ib]) for ib in ib_range}
for ia in range(round(A_WINDOW[0] * fps), round(A_WINDOW[1] * fps) + 1, round(STEP * fps)):
fa = features(a[ia])
for ib in ib_range:
m = alignment(fa, fb[ib])
if m is None or not covers_frame(m):
continue
diff = cv2.absdiff(moved(a[ia], m), b[ib]).mean()
if best is None or diff < best[0]:
best = (diff, ia, ib, m)
if best is None:
sys.exit("No matching frames found: widen A_WINDOW or B_WINDOW")
return best[1:]
src_a, src_b, out = sys.argv[1:4]
a, fps = read_frames(src_a)
b, _ = read_frames(src_b)
h, w = a[0].shape[:2]
ia, ib, m = best_cut()
cut_a, cut_b = (ia + 1) / fps, ib / fps
print(f"cutting from A at {cut_a:.2f}s to B at {cut_b:.2f}s")
writer = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "bgr24", "-s", f"{w}x{h}",
"-r", str(fps), "-i", "-", "-i", src_a, "-i", src_b, "-filter_complex",
f"[1:a]atrim=0:{cut_a},afade=out:st={cut_a - 0.02}:d=0.02[aa];"
f"[2:a]atrim={cut_b},asetpts=PTS-STARTPTS,afade=in:d=0.02[ab];[aa][ab]concat=v=0:a=1[au]",
"-map", "0:v", "-map", "[au]", "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p",
"-c:a", "aac", out], stdin=subprocess.PIPE)
start = ia - round(ALIGN * fps)
for i in range(ia + 1):
p = min(max((i - start) / (ia - start), 0), 1)
p = p * p * (3 - 2 * p) # ease in and out
step = np.float32([[1, 0, 0], [0, 1, 0]]) * (1 - p) + m * p
writer.stdin.write((moved(a[i], step) if p > 0 else a[i]).tobytes())
for img in b[ib:]:
writer.stdin.write(img.tobytes())
writer.stdin.close()
if writer.wait():
sys.exit("ffmpeg failed")





