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
Add a funhouse mirror to input.mp4 Second the mirror warp starts: 1.0 Second it's gone again: 5.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.
Add a funhouse mirror to @clip1.mp4, put a sale badge on @clip2.mp4, give @clip3.mp4 a VHS look, then join all three with crossfades.
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 |
|---|---|---|
START | 1.0 | Second the mirror warp starts. |
END | 5.0 | Second it's gone again. |
EASE | 0.5 | Seconds it takes to build up at the start and fade out at the end. |
BULGE | 0.8 | Extra width in the fattest band. 0.8 is 80% wider. |
BANDS | 2.0 | Fat-and-thin waves from the top of the frame to the bottom. |
ROLL | 2.5 | Seconds for the bands to roll down one wave. Bigger is slower. |
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
Add a funhouse mirror effect to input.mp4 from 1.0 to 5.0 seconds; before 1.0 and after 5.0 seconds the video is unchanged. Strength k eases from 0 to 1 over the first 0.5 seconds and back to 0 over the last 0.5 seconds (smoothstep k = u²(3 − 2u), where u = min(t − 1, 5 − t, 0.5) / 0.5). Each row is stretched sideways around the frame's vertical center line by its own amount: output pixel (x, y) shows the input pixel at column w/2 + (x − w/2) / s on the same row, with s = 1 + 0.8 × k × (1 + sin(2π × (2 × y / h − (t − 1) / 2.5))) / 2, where w and h are the frame width and height. So there are 2 fat-and-thin waves from top to bottom, the fattest rows are 1.8x wider at full strength, the thinnest keep their width, and the bands roll down one wave every 2.5 seconds. Only columns move, never rows, and the center column stays put. Use Python with OpenCV (cv2.remap, cubic interpolation) and ffmpeg, keep the size, 30fps and the audio, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as funhouse-mirror.py and run:
python3 funhouse-mirror.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Funhouse mirror: the picture swells fat and pinches thin in bands that roll down the frame, like a carnival mirror.
Usage: python3 funhouse-mirror.py input.mp4 output.mp4
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
START = 1.0 # second the mirror warp starts
END = 5.0 # second it's gone again
EASE = 0.5 # seconds it takes to build up at the start and fade out at the end
BULGE = 0.8 # extra width in the fattest band (0.8 = 80% wider); the thinnest band keeps its width
BANDS = 2.0 # fat-and-thin waves from the top of the frame to the bottom
ROLL = 2.5 # seconds for the bands to roll down one wave
def strength(t):
"""0 outside the effect, easing up to 1 and back down over EASE seconds at each end."""
u = max(min(t - START, END - t, EASE) / EASE, 0)
return u * u * (3 - 2 * u)
def source_columns(t):
"""For every pixel, the input column it shows: each row is scaled across the frame's center line
by its own amount, between 1 (thin bands) and 1 + BULGE (fat bands)."""
wave = np.sin(2 * math.pi * (BANDS * (rows[:, :1] + 0.5) / h - (t - START) / ROLL))
scale = 1 + strength(t) * BULGE * (1 + wave) / 2
return (w / 2 + (cols - w / 2) / scale).astype(np.float32)
def probe(path):
info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
"stream=width,height,r_frame_rate", "-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)
src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
rows, cols = np.mgrid[0:h, 0:w].astype(np.float32) + 0.5
reader = subprocess.Popen(["ffmpeg", "-v", "error", "-i", src, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
stdout=subprocess.PIPE)
writer = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
"-r", rate, "-i", "-", "-i", src, "-map", "0:v", "-map", "1:a?", "-c:v", "libx264",
"-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
stdin=subprocess.PIPE)
i = 0
while True:
buf = reader.stdout.read(w * h * 3)
if len(buf) < w * h * 3:
break
img = np.frombuffer(buf, np.uint8).reshape(h, w, 3)
t = i / fps
if START < t < END:
img = cv2.remap(img, source_columns(t) - 0.5, rows - 0.5, cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT)
writer.stdin.write(img.tobytes())
i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
sys.exit("ffmpeg failed")





