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 kaleidoscope transition Length of the whole transition in seconds: 1.2 Seconds the kaleidoscope circle takes to spread over the frame, and to shrink away at the end: 0.4
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 kaleidoscope 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.2 | Length of the whole transition in seconds. |
FOLD | 0.4 | Seconds the kaleidoscope circle takes to spread over the frame, and to shrink away at the end. |
SWAP | 0.2 | Seconds in the middle over which A's pattern dissolves into B's. |
WEDGES | 6 | How many mirrored wedges fill the circle. Keep it even; 8 or 12 gives a busier star. |
SPIN | 90 | Degrees per second the pattern turns clockwise. 0 holds it still. |
FEATHER | 40 | Pixels of soft edge on the growing and shrinking circle. |
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, 30fps) with a 1.2 second kaleidoscope transition: A plays untouched until 4.8s, the transition runs from 4.8s to 6.0s over the last 1.2s of A and the first 1.2s of B, then B carries on from its 1.2s mark, for a 10.8 second output. The kaleidoscope: take the 60-degree wedge pointing straight up from the frame's center (270,480), from 30 degrees left of vertical to 30 degrees right, and repeat it six times around the center, mirroring every other copy so the seams line up, with no scaling (a pixel's distance from the center stays the same; clamp samples to the frame edge). The pattern turns clockwise at 90 degrees per second and is at 0 degrees (wedge pointing straight up, so the bottom half is an exact upside-down mirror of the top half) at 5.2s. For each output pixel: take its angle clockwise from straight up, subtract the pattern's turn, add 30, take it modulo 120, fold anything above 60 back to (120 - angle), subtract 30, and sample the clip at that angle and the same distance from the center. The kaleidoscope shows inside a circle around the center with a 40-pixel soft edge: from 4.8s to 5.2s its radius grows, with a smoothstep ease, from 0 to the corner distance plus 40 pixels (fully covering the frame at 5.2s), and from 5.6s to 6.0s it shrinks the same way back to 0. Outside the circle show plain A for the first 0.6s of the transition and plain B for the last 0.6s. Inside it the pattern is made from A's frame, dissolving (smoothstep) into the same pattern made from B's frame between 5.3s and 5.5s. Crossfade the audio over the same 1.2 seconds. There's no text; if you add any, draw it with Pillow, not ffmpeg's drawtext. Use Python with numpy (or OpenCV's remap) and ffmpeg, encode H.264 yuv420p, keep the audio and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as kaleidoscope-transition.py and run:
python3 kaleidoscope-transition.py clipA.mp4 clipB.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Kaleidoscope transition: clip A folds into a spinning six-wedge kaleidoscope from the center out, the pattern turns into clip B, then unfolds into B.
Usage: python3 kaleidoscope-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
import cv2
import numpy as np
D = 1.2 # transition length in seconds
FOLD = 0.4 # seconds the kaleidoscope takes to spread over the frame, and to shrink away again at the end
SWAP = 0.2 # seconds, centered on the middle, over which A's pattern dissolves into B's
WEDGES = 6 # mirrored wedges around the center (an even number keeps every seam a clean mirror)
SPIN = 90 # degrees per second the pattern turns clockwise; it points straight up the moment A is fully folded
FEATHER = 40 # pixels of soft edge on the growing and shrinking circle
def probe(path):
info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
"stream=width,height,r_frame_rate:format=duration", "-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), float(info["format"]["duration"])
def frames(path, start):
"""The transition's frames from path, starting at `start` seconds, fitted to A's size and frame rate."""
raw = subprocess.check_output(["ffmpeg", "-v", "error", "-ss", f"{start:.3f}", "-i", path, "-frames:v", str(n),
"-vf", f"scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={rate}",
"-f", "rawvideo", "-pix_fmt", "rgb24", "-"])
return [np.frombuffer(raw[i * w * h * 3:(i + 1) * w * h * 3], np.uint8).reshape(h, w, 3) for i in range(n)]
def kaleidoscope_maps(degrees):
"""Where each output pixel samples from: the wedge straight above the center, repeated WEDGES times around it,
every other copy mirrored, the whole pattern turned clockwise by `degrees`. Distances from the center stay the same."""
wedge = 2 * math.pi / WEDGES
a = np.mod(theta - math.radians(degrees) + wedge / 2, 2 * wedge)
a = np.where(a < wedge, a, 2 * wedge - a) - wedge / 2 # fold into the source wedge, -wedge/2 to +wedge/2
return (cx + r * np.sin(a)).astype(np.float32), (cy - r * np.cos(a)).astype(np.float32)
def smooth(p):
p = min(max(p, 0), 1)
return p * p * (3 - 2 * p)
def transition_frame(i):
"""Frame i: a kaleidoscope circle grows from the center over A, A's pattern dissolves into B's, the circle shrinks onto B."""
t = i / fps
mx, my = kaleidoscope_maps(SPIN * (t - FOLD))
mix = smooth((t - (D - SWAP) / 2) / SWAP)
pattern = cv2.addWeighted(cv2.remap(a_frames[i], mx, my, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE), 1 - mix,
cv2.remap(b_frames[i], mx, my, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE), mix, 0)
straight = a_frames[i] if t < D / 2 else b_frames[i]
cover = smooth(t / FOLD) if t < D / 2 else smooth((D - t) / FOLD)
alpha = np.clip((cover * (corner + FEATHER) - r) / FEATHER, 0, 1)[..., None]
return (pattern * alpha + straight * (1 - alpha)).astype(np.uint8)
a_path, b_path, out = sys.argv[1:4]
w, h, rate, fps, a_len = probe(a_path)
n = round(D * fps)
offset = a_len - D
a_frames, b_frames = frames(a_path, offset), frames(b_path, 0)
# polar coordinates of every output pixel, worked out once
cx, cy = w / 2, h / 2
ys, xs = np.mgrid[0:h, 0:w].astype(np.float32)
r = np.hypot(xs - cx, ys - cy) # distance from the center
theta = np.arctan2(xs - cx, cy - ys) # angle, clockwise from straight up
corner = math.hypot(cx, cy)
# A up to the transition, the kaleidoscope frames, then B from where the transition ends; the sound crossfades over D
ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", a_path, "-i", b_path,
"-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
"-filter_complex",
f"[0:v]trim=end={offset:.4f},setpts=PTS-STARTPTS,fps={rate},format=yuv420p,setsar=1[a];"
f"[1:v]scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={rate},"
f"trim=start={D},setpts=PTS-STARTPTS,format=yuv420p,setsar=1[b];"
f"[2:v]format=yuv420p,setsar=1[k];"
f"[a][k][b]concat=n=3:v=1:a=0[v];[0:a][1:a]acrossfade=d={D}[au]",
"-map", "[v]", "-map", "[au]", "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p",
"-c:a", "aac", out], stdin=subprocess.PIPE)
for i in range(n):
ff.stdin.write(transition_frame(i).tobytes())
ff.stdin.close()
if ff.wait():
sys.exit("ffmpeg failed")





