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 rack focus to input.mp4 Out-of-focus blur (Gaussian sigma) as a share of the frame width: 13.5 pixels on a 540-wide video: 0.025 Second the focus pull starts: 1.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 rack focus 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 |
|---|---|---|
BLUR | 0.025 | Out-of-focus blur (Gaussian sigma) as a share of the frame width: 13.5 pixels on a 540-wide video. |
START | 1.5 | Second the focus pull starts. |
PULL | 1.0 | Seconds the focus takes to move from the product to the backdrop. |
MASK_EVERY | 3 | Cut the product out on every Nth frame and reuse the mask in between. 1 is most accurate, higher is faster. |
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 rack focus to input.mp4 (540x960, 30fps, 6 seconds) with Python: use rembg (session "u2net") to cut the bottle out of the frames as a soft mask, OpenCV (cv2.GaussianBlur) for the blur and ffmpeg to read and write the video. Treat the bottle and everything else (the backdrop and the acrylic block) as two layers. Until 1.5 seconds the bottle is fully sharp and the backdrop is blurred with a Gaussian sigma of 13.5 pixels (2.5% of the frame width). From 1.5 to 2.5 seconds, with a smoothstep ease, the backdrop's blur goes from 13.5 to 0 while the bottle's goes from 0 to 13.5; after 2.5 seconds the backdrop stays fully sharp and the bottle fully blurred to the end. Blur the bottle as its own layer (blur the frame times the mask and the mask itself by the same sigma, then put it over the backdrop with the blurred mask as alpha) so its soft edge spreads over the sharp backdrop, and before blurring the backdrop fill the bottle's hole with the surrounding backdrop colors so no purple smears into it. Don't zoom, darken or move the frame. There's no text to draw, but if you add any, draw it with Pillow, not ffmpeg's drawtext. Keep the size, frame rate and audio, encode H.264 yuv420p and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as rack-focus.py and run:
python3 rack-focus.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Rack focus: the shot opens sharp on the product with a soft backdrop, then focus pulls back so the product goes soft and the backdrop comes sharp.
Usage: python3 rack-focus.py input.mp4 output.mp4
Needs ffmpeg, OpenCV, numpy, Pillow and rembg to cut the product out of every frame
(pip install opencv-python-headless numpy pillow "rembg[cpu]"; rembg downloads its u2net model on first use).
"""
import json
import subprocess
import sys
import cv2
import numpy as np
from PIL import Image
from rembg import new_session
BLUR = 0.025 # out-of-focus blur (Gaussian sigma) as a share of the frame width: 13.5 px on a 540-wide video
START = 1.5 # second the focus pull starts
PULL = 1.0 # seconds it takes to move from the product to the backdrop
MASK_EVERY = 3 # cut the product out on every Nth frame and reuse it in between (the camera moves slowly)
def blur(img, sigma):
return cv2.GaussianBlur(img, (0, 0), sigma, borderType=cv2.BORDER_REFLECT) if sigma > 0.3 else img
def paint_out(img, keep):
"""Fill the product's hole with the backdrop colors around it (worked out at quarter size, as it's smooth)."""
small = (w // 4, h // 4)
color = blur(cv2.resize(img * keep, small, interpolation=cv2.INTER_AREA), w * 0.0125)
weight = blur(cv2.resize(keep, small, interpolation=cv2.INTER_AREA), w * 0.0125)
return cv2.resize(color / np.maximum(weight, 1e-3)[:, :, None], (w, h), interpolation=cv2.INTER_LINEAR)
def focus(t):
"""0 while the product is in focus, easing to 1 once the backdrop is."""
p = min(max((t - START) / PULL, 0), 1)
return p * p * (3 - 2 * p)
def rack(rgb, mask, t):
"""Defocus the product and the backdrop as two layers, each by its own amount, and stack them."""
img = rgb.astype(np.float32)
# the backdrop with the product painted out, so blurring it doesn't smear the product's colors around
keep = 1 - cv2.dilate(mask, np.ones((9, 9), np.uint8))[:, :, None]
plate = img * keep + paint_out(img, keep) * (1 - keep)
backdrop = blur(plate, BLUR * w * (1 - focus(t)))
# the product as a layer with its own soft edge, spreading over the backdrop as it goes out of focus
sigma = BLUR * w * focus(t)
product, alpha = blur(img * mask[:, :, None], sigma), blur(mask, sigma)[:, :, None]
return (product + backdrop * (1 - alpha)).clip(0, 255).round().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)
def render(src, out):
"""Read src frame by frame, rack the focus and save to out, keeping the audio."""
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):
rgb = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
if i % MASK_EVERY == 0:
mask = np.asarray(session.predict(Image.fromarray(rgb))[0], np.float32) / 255
writer.stdin.write(rack(rgb, mask, i / fps).tobytes())
i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
sys.exit("ffmpeg failed")
src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
session = new_session("u2net")
render(src, out)





