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 torn paper edge to input.mp4 Second the edges start to tear: 0.5 Seconds the tear takes to reach full depth: 0.5
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 torn paper edge 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 edges start to tear. |
TEAR | 0.5 | Seconds the tear takes to reach full depth. |
BORDER | 0.06 | Average depth of the backdrop strip, as a share of the frame width. |
RAGGED | 0.45 | How much that depth wanders along each edge (0 is a straight edge). |
RIM | 0.025 | Average width of the white torn-paper rim, as a share of the frame width. |
PAPER, BACKDROP | (252, 250, 245), (196, 158, 108) | Rim and backdrop colors. |
SHADOW | 0.35 | Strength of the paper's shadow on the backdrop (0-1). |
SEED | 7 | Change it for a different tear. |
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
Give input.mp4 a torn paper edge. Play it untouched until 0.5 seconds, then over 0.5 seconds (ease-out) tear all four edges inward and hold that to the end. Outside the tear is a kraft-brown (196, 158, 108) backdrop with faint grain. Its edge sits on average 6% of the frame width in from each side, wandering up to 45% either way along the edge with big bumps and small fibrous ones (a fixed random seed, so it doesn't flicker). Just inside it is a ragged off-white (252, 250, 245) paper rim, on average 2.5% of the frame width wide and varying by up to 60%, and inside the rim the video plays unscaled. The paper casts a soft shadow onto the backdrop: blurred by 1.2% of the frame width, shifted 1.2% down and 0.8% right, at 35% strength. Use Python with numpy and Pillow for the masks (draw anything with Pillow, not ffmpeg's drawtext) and ffmpeg to read and write the video, keep the audio and the original size, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as torn-paper-edge.py and run:
python3 torn-paper-edge.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Torn paper edge: the video's edges tear away into a ragged white paper rim over a kraft-paper backdrop.
Usage: python3 torn-paper-edge.py input.mp4 output.mp4
Needs ffmpeg, Pillow and numpy (pip install pillow numpy).
"""
import json
import subprocess
import sys
import numpy as np
from PIL import Image, ImageFilter
START = 0.5 # second the edges start to tear
TEAR = 0.5 # seconds the tear takes to reach full depth
BORDER = 0.06 # average depth of the backdrop strip, as a share of the frame width
RAGGED = 0.45 # how much that depth wanders along each edge (0 = straight)
RIM = 0.025 # average width of the white torn-paper rim, as a share of the frame width
PAPER = (252, 250, 245) # rim color (off-white)
BACKDROP = (196, 158, 108) # backdrop color (kraft brown)
SHADOW = 0.35 # strength of the shadow the paper casts on the backdrop (0-1)
SEED = 7 # change for a different tear
def ragged(n, rng):
"""A wandering line of n values around 0 (-1 to 1): big bumps plus small fibrous ones."""
line = np.zeros(n)
for cells, amp in [(5, 0.5), (22, 0.3), (110, 0.2)]:
knots = rng.uniform(-1, 1, cells + 1)
line += amp * np.interp(np.linspace(0, cells, n), np.arange(cells + 1), knots)
return line
def tear_lines():
"""For each side (left, right, top, bottom): how deep the backdrop and the end of the white rim reach."""
rng = np.random.default_rng(SEED)
sides = {}
for side, n in [("left", h), ("right", h), ("top", w), ("bottom", w)]:
paper = w * BORDER * (1 + RAGGED * ragged(n, rng))
rim = w * RIM * (1 + 0.6 * ragged(n, rng))
sides[side] = (paper, paper + rim)
return sides
def coverage(depth):
"""0-1 mask of what lies inside the four edges, each `depth[side]` pixels in, with soft 1px edges."""
x, y = np.arange(w)[None, :], np.arange(h)[:, None]
return np.clip(np.minimum.reduce([x - depth["left"][:, None], (w - 1 - x) - depth["right"][:, None],
y - depth["top"][None, :], (h - 1 - y) - depth["bottom"][None, :]]) + 0.5, 0, 1)
def layers(p):
"""Paper mask, video mask and shadowed backdrop with the tear at progress p (0-1)."""
paper = coverage({s: p * lines[s][0] for s in lines})
video = coverage({s: p * lines[s][1] for s in lines})
blur = Image.fromarray(np.uint8(paper * 255)).filter(ImageFilter.GaussianBlur(w * 0.012))
shade = np.roll(np.asarray(blur, np.float32) / 255, (round(w * 0.012), round(w * 0.008)), (0, 1))
return paper[..., None], video[..., None], backdrop * (1 - SHADOW * shade[..., None])
def torn_frame(video, t):
p = min(max((t - START) / TEAR, 0), 1)
if p == 0:
return video
p = 1 - (1 - p) ** 3 # ease out
paper, inside, bg = final if p == 1 else layers(p)
img = bg * (1 - paper) + np.array(PAPER, np.float32) * paper
img = img * (1 - inside) + np.asarray(video, np.float32) * inside
return Image.fromarray(np.uint8(img.round()))
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)
lines = tear_lines()
grain = np.asarray(Image.effect_noise((w, h), 40).filter(ImageFilter.GaussianBlur(1)), np.float32)
backdrop = np.array(BACKDROP, np.float32) + (grain[..., None] - 128) * 0.15
final = layers(1)
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 chunk := reader.stdout.read(w * h * 3):
writer.stdin.write(torn_frame(Image.frombytes("RGB", (w, h), chunk), i / fps).tobytes())
i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
sys.exit("ffmpeg failed")





