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 tunnel effect to input.mp4 Seconds the picture starts curling into the tunnel and is back to normal: 1.0, 5.0 Seconds the curl in and the unroll out each take: 0.6
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 tunnel effect 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, END | 1.0, 5.0 | Seconds the picture starts curling into the tunnel and is back to normal. |
MORPH | 0.6 | Seconds the curl in and the unroll out each take. |
DEPTH | 0.5 | How stretched the walls are. Smaller pulls the picture deeper into the distance. |
SPEED | 0.9 | How fast you fly down the tunnel, in picture heights per second. |
SPIN | 0.08 | How fast the tunnel turns, in turns per second. 0 stops it. |
WRAPS | 2 | How many mirrored copies of the picture run round the wall. |
FOG | 0.3 | Size of the dark far end, as a share of half the frame's short side. |
FOG_COLOR | (10, 6, 24) | Color the far end fades into. |
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 tunnel effect to input.mp4 with Python and numpy, decoding and encoding the frames with ffmpeg. Measure each pixel from the frame center: its distance r as a share of half the frame's short side (r = 1 at 270 px on a 540x960 frame) and its angle a from straight up, in turns of half a circle (a = atan2(x - w/2, h/2 - y) / pi, from -1 to 1). In the full tunnel, the pixel shows the input frame at x = fold(2 x (a + 0.16 x (t - 1))) x (width - 1) and y = fold(0.5 / r + 0.9 x (t - 1)) x (height - 1), where t is the time in seconds and fold(v) = |((v + 1) mod 2) - 1| (a 0 -> 1 -> 0 zigzag, so the picture repeats mirrored with no seams), sampled with bilinear blending. So the picture runs twice round the wall, mirrored, turns 0.08 turns per second, and stretches into the distance toward the center while you fly forward. Fade the far end into a dark (10, 6, 24): mix that color in by 1 - min(r / 0.3, 1)^1.5. Blend between normal and tunnel by moving each pixel's sample position from its own position to the tunnel position, and the fog with it, by a strength that eases (smoothstep) from 0 at 1.0s to 1 at 1.6s, stays 1 until 4.4s and eases back to 0 at 5.0s; before 1.0s and after 5.0s the frames are untouched. 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 tunnel-effect.py and run:
python3 tunnel-effect.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Tunnel effect: at 1 second the picture curls round into a tunnel that stretches away to a dark center, flies forward through it, then unrolls back at 5 seconds.
Usage: python3 tunnel-effect.py input.mp4 output.mp4
Needs ffmpeg and numpy (pip install numpy).
"""
import json
import subprocess
import sys
import numpy as np
START = 1.0 # second the picture starts curling into the tunnel
END = 5.0 # second it is back to normal
MORPH = 0.6 # seconds the curl in (and the unroll out) takes
FPS = 30 # output frame rate
DEPTH = 0.5 # how stretched the walls are: smaller pulls the picture deeper into the distance
SPEED = 0.9 # how fast you fly down the tunnel (picture heights per second)
SPIN = 0.08 # how fast the tunnel turns (turns per second)
WRAPS = 2 # how many times the picture runs round the tunnel wall (each one mirrored)
FOG = 0.3 # radius of the dark center, as a share of half the frame's short side
FOG_COLOR = (10, 6, 24) # color the far end of the tunnel fades into
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 RGB frames at FPS."""
ff = subprocess.Popen(["ffmpeg", "-v", "error", "-i", path, "-vf", f"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 fold(x):
"""0 -> 1 -> 0 every 2 units, so the picture repeats mirrored with no seams."""
return np.abs((x + 1) % 2 - 1)
def strength(t):
"""How far into the tunnel the picture is at time t (0 = normal, 1 = full tunnel), eased in and out."""
p = max(min((t - START) / MORPH, (END - t) / MORPH, 1), 0)
return p * p * (3 - 2 * p)
def sample(frame, sx, sy):
"""Read the frame at fractional pixel positions, blending the four nearest pixels."""
x0, y0 = np.floor(sx).astype(int), np.floor(sy).astype(int)
fx, fy = (sx - x0)[..., None], (sy - y0)[..., None]
x1, y1 = np.minimum(x0 + 1, w - 1), np.minimum(y0 + 1, h - 1)
top = frame[y0, x0] * (1 - fx) + frame[y0, x1] * fx
bottom = frame[y1, x0] * (1 - fx) + frame[y1, x1] * fx
return top * (1 - fy) + bottom * fy
def tunnel(frame, t, e):
"""One frame at tunnel strength e: each pixel's angle round the center picks a point across the picture
and its distance picks a point down it (depth / distance, so it stretches toward the center)."""
angle = WRAPS * (np.arctan2(xs - w / 2, h / 2 - ys) / np.pi + 2 * SPIN * (t - START))
tx = fold(angle) * (w - 1)
ty = fold(DEPTH / np.maximum(radius, 1e-3) + SPEED * (t - START)) * (h - 1)
sx = np.clip((1 - e) * xs + e * tx, 0, w - 1)
sy = np.clip((1 - e) * ys + e * ty, 0, h - 1)
fog = e * (1 - np.clip(radius / FOG, 0, 1) ** 1.5)[..., None]
return sample(frame.astype(np.float32), sx, sy) * (1 - fog) + np.array(FOG_COLOR, np.float32) * fog
src, out = sys.argv[1:3]
w, h, duration = probe(src)
ys, xs = np.mgrid[0:h, 0:w].astype(np.float32)
radius = np.hypot(xs - w / 2, ys - h / 2) / (min(w, h) / 2)
enc = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
"-r", str(FPS), "-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)
for i, frame in enumerate(frames(src)):
e = strength(i / FPS)
enc.stdin.write((tunnel(frame, i / FPS, e).astype(np.uint8) if e > 0 else frame).tobytes())
enc.stdin.close()
if enc.wait():
sys.exit("ffmpeg failed")





