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 stained glass to input.mp4 Second the picture turns into stained glass: 1.0 Average pane size in pixels: 30
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 stained glass 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 picture turns into stained glass. |
CELL | 30 | Average pane size in pixels. Bigger gives fewer, chunkier panes. |
LEAD | 5 | Lead line thickness in pixels. |
LEAD_COLOR | (22, 20, 24) | Color of the lead lines. |
SATURATION | 2.2 | Color boost for the panes. |
BEVEL | 10 | Pixels over which each pane darkens toward its lead. |
SEED | 3 | Random seed for the pane layout. |
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
Turn input.mp4 (540x960) into a stained glass window from 1.0 seconds to the end; before 1.0 seconds leave it untouched. Lay out the panes once, with a fixed random seed so they stay in place from frame to frame: scatter 576 random points over the frame (one per 30x30 pixels on average) and give every pixel to its nearest point, making irregular Voronoi cells. Mark the pixels where neighboring cells meet, plus a 1 pixel border around the frame, and thicken them with a 5x5 elliptical dilation into lead lines. For each frame from 1.0 s, fill every cell with the average color of the frame under it, push that color away from its gray by 2.2x (clip to 0-255), darken each pixel of a pane toward its lead to 72% at the lead and 100% from 10 pixels in, for a beveled glass look, and draw the lead lines over it in near-black (22, 20, 24) with a slightly softened edge. The lead should cover about a quarter of the frame. Use Python with numpy and OpenCV for the frames and ffmpeg to read and write the video (don't use ffmpeg's drawtext if you add any text), keep the size, 30fps, the 6 second length and the audio, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as stained-glass.py and run:
python3 stained-glass.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Stained glass: at one second the shot breaks into irregular flat panes of glowing color held in thick dark lead lines.
Usage: python3 stained-glass.py input.mp4 output.mp4
Needs ffmpeg, numpy and OpenCV for the cell layout (pip install numpy opencv-python-headless).
"""
import json
import subprocess
import sys
import cv2
import numpy as np
START = 1.0 # second the picture turns into stained glass
CELL = 30 # average pane size in pixels
LEAD = 5 # lead line thickness in pixels
LEAD_COLOR = (22, 20, 24) # color of the lead lines
SATURATION = 2.2 # color boost for the panes
BEVEL = 10 # pixels over which each pane darkens toward its lead, like thick glass
SEED = 3 # same seed, same pane layout: the lead stays put and only the colors change
def layout(w, h):
"""Irregular panes: each pixel labeled with its nearest random seed point (a Voronoi diagram),
plus the lead mask along the borders and each pixel's shading from its distance to the lead."""
rng = np.random.default_rng(SEED)
count = round(w * h / CELL ** 2)
seeds = np.full((h, w), 255, np.uint8)
seeds[rng.integers(0, h, count), rng.integers(0, w, count)] = 0
_, labels = cv2.distanceTransformWithLabels(seeds, cv2.DIST_L2, 5, labelType=cv2.DIST_LABEL_CCOMP)
border = np.zeros((h, w), np.uint8)
border[:, 1:] |= labels[:, 1:] != labels[:, :-1]
border[1:, :] |= labels[1:, :] != labels[:-1, :]
border[[0, -1], :] = border[:, [0, -1]] = 1
lead = cv2.dilate(border, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (LEAD, LEAD)))
to_lead = cv2.distanceTransform(1 - lead, cv2.DIST_L2, 5)
shade = 0.72 + 0.28 * np.clip(to_lead / BEVEL, 0, 1)
edge = cv2.GaussianBlur(lead.astype(np.float32), (3, 3), 0)[..., None] # soften the lead's edge
return labels.ravel(), labels.max() + 1, shade[..., None], edge
def stained(img):
"""Fill every pane with the average color of the frame under it, saturated and beveled, then lead it."""
flat = img.reshape(-1, 3).astype(np.float32)
area = np.bincount(labels, minlength=panes)
means = np.stack([np.bincount(labels, flat[:, c], panes) for c in range(3)], 1) / np.maximum(area, 1)[:, None]
gray = means.mean(1, keepdims=True)
means = np.clip(gray + (means - gray) * SATURATION, 0, 255)
glass = means[labels].reshape(img.shape) * shade
return (glass * (1 - edge) + np.array(LEAD_COLOR, np.float32) * edge).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)
labels, panes, shade, edge = layout(w, h)
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 raw := reader.stdout.read(w * h * 3):
img = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
if i / fps >= START:
img = stained(img)
writer.stdin.write(img.tobytes())
i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
sys.exit("ffmpeg failed")





