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 prism light to input.mp4 Ghosts: rose, green, violet
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 prism light 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 | 0.5 | Second the prism light fades in. |
FRINGE | 8 | Pixels the red and blue channels move apart, each way. |
GHOSTS | rose, green, violet | Tint of each ghost copy; add or remove colors for more or fewer copies. |
GHOST_STEP | (36, -12) | How far each ghost sits from the one before, in pixels right and down. |
GHOST_OPACITY | 0.14 | Strength of each ghost copy. |
BAND_OPACITY | 0.45 | Strength of the rainbow band. |
SWEEP | 2.5 | Seconds the rainbow band takes to cross the frame. |
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 prism light effect to input.mp4, fading in from 0.5 to 0.9 seconds and staying to the end. Work on each frame with Python and numpy, with ffmpeg reading and writing the video. 1) Color fringe: shift the red channel 8 pixels left and the blue channel 8 pixels right, repeating edge pixels into the gap. 2) Ghosts: add three copies of the original frame, offset 36, 72 and 108 pixels right and 12, 24 and 36 pixels up, tinted rose (255, 90, 160), green (110, 255, 140) and violet (110, 130, 255). Tint a copy by multiplying each channel by 1 + 0.8 x (2 x tint/255 - 1), and add 0.14 x (tinted copy - original frame) for each. 3) Rainbow band: a diagonal band running from top right to bottom left, 30% of the frame diagonal wide (measured along (x + y) / (width + height)), whose hue runs through one full cycle across it (red, green, blue, back to red; channel c = 0.5 + 0.5 x cos(2 pi x (position - c/3)) for c = 0, 1, 2), at strength 0.45 at its center falling off as exp(-(2.5 x offset / width)^2). It sweeps from the top-left corner to the bottom-right corner every 2.5 seconds starting at 0.5 seconds. Apply it by multiplying each channel by 1 + strength x (2 x band color - 1). Leave the first 0.5 seconds untouched, keep the size, 30fps and the audio, and save it as output.mp4. There's no text to draw.Run it without Claude
This is the exact script we tested. Save it as prism-light.py and run:
python3 prism-light.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Prism light: rainbow-tinted ghost copies, color-fringed edges and a rainbow band that sweeps across the frame.
Usage: python3 prism-light.py input.mp4 output.mp4
Needs ffmpeg and numpy (pip install numpy).
"""
import json
import subprocess
import sys
import numpy as np
START = 0.5 # second the prism light fades in
FADE = 0.4 # seconds the fade-in takes
FRINGE = 8 # pixels the red channel moves left and the blue channel moves right
GHOSTS = [(255, 90, 160), (110, 255, 140), (110, 130, 255)] # tint of each ghost copy: rose, green, violet
GHOST_STEP = (36, -12) # pixels each ghost sits from the one before (right, down)
GHOST_OPACITY = 0.14 # strength of each ghost copy
BAND_WIDTH = 0.3 # rainbow band width as a share of the frame diagonal
BAND_OPACITY = 0.45 # strength of the rainbow band at its center
SWEEP = 2.5 # seconds the band takes to cross the frame, top left to bottom right
def shift(img, dx, dy):
"""Move an image dx pixels right and dy pixels down, repeating the edge pixels into the gap."""
h, w = img.shape[:2]
ys = np.clip(np.arange(h) - dy, 0, h - 1)
xs = np.clip(np.arange(w) - dx, 0, w - 1)
return img[ys][:, xs]
def tint(img, color, amount):
"""Push img's colors toward color (0-1 RGB) by amount, keeping its detail: 1 doubles the matching channels."""
return img * (1 + amount * (2 * color - 1))
def rainbow_band(t):
"""The rainbow band at time t: colors (h, w, 3) and how strong it is at each pixel (h, w, 1)."""
pos = -BAND_WIDTH + ((t - START) / SWEEP % 1) * (1 + 2 * BAND_WIDTH)
across = (diag - pos) / BAND_WIDTH # -0.5..0.5 inside the band
hue = (across + 0.5)[..., None]
colors = 0.5 + 0.5 * np.cos(2 * np.pi * (hue - np.array([0, 1 / 3, 2 / 3])))
strength = np.exp(-(across * 2.5) ** 2)[..., None]
return colors, strength
def prism(frame, t):
amount = np.clip((t - START) / FADE, 0, 1)
if amount == 0:
return frame
img = frame / 255
out = img.copy()
out[..., 0] = shift(img[..., 0], -FRINGE, 0)
out[..., 2] = shift(img[..., 2], FRINGE, 0)
for k, color in enumerate(GHOSTS, 1):
ghost = tint(shift(img, GHOST_STEP[0] * k, GHOST_STEP[1] * k), np.array(color) / 255, 0.8)
out += (ghost - img) * GHOST_OPACITY
colors, strength = rainbow_band(t)
out = tint(out, colors, strength * BAND_OPACITY)
out = img + (out - img) * amount
return (out * 255).round().clip(0, 255).astype(np.uint8)
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)
y, x = np.mgrid[0:h, 0:w]
diag = (x + y) / (w + h) # 0 at the top left, 1 at the bottom right
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:
raw = reader.stdout.read(w * h * 3)
if len(raw) < w * h * 3:
break
frame = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
writer.stdin.write(prism(frame, i / fps).tobytes())
i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
sys.exit("ffmpeg failed")





