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 background replacement to input.mp4 Wall: (250, 150, 105) to (255, 205, 160) Floor: (240, 175, 135) to (205, 130, 95)
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 background replacement 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 new background starts to dissolve in. |
FADE | 0.5 | Seconds the dissolve takes. |
WALL | (250, 150, 105) to (255, 205, 160) | Wall gradient, top to floor line. |
SUN | (255, 238, 210) | Color of the soft disc behind the product. |
SUN_Y, SUN_R | 0.40, 0.42 | Disc center down the frame and radius as a share of the width. |
FLOOR_Y | 0.78 | Where the floor starts, 0-1 down the frame; set it at the product's base. |
FLOOR | (240, 175, 135) to (205, 130, 95) | Floor gradient, back to front. |
SHADOW | 0.45 | Darkness of the contact shadow under the product. |
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
Replace the background of input.mp4 (540x960, the camera circles a serum bottle whose base sits about 79% down the frame). Build the new background once with Python, Pillow and numpy: a wall that is a vertical gradient from (250, 150, 105) at the top to (255, 205, 160) at 78% down the frame, then a floor from 78% down to the bottom that goes from (240, 175, 135) to (205, 130, 95), plus a soft cream (255, 238, 210) disc on the wall centered horizontally and 40% down, with a radius of 42% of the frame width and edges blurred by 4% of the width (keep it off the floor). Leave the video untouched before 1.0 second. From 1.0 second, on every frame cut the bottle out with rembg (u2net model), dissolve the old background into the new one over 0.5 seconds with a smoothstep ease, add a soft dark contact shadow (45% darkness, a blurred ellipse as wide as 1.2 times the bottle) under the bottle's base that follows it, and put the cut-out bottle back on top unchanged. Use ffmpeg to read and write the frames (no text is drawn; if you add any, draw it with Pillow, not ffmpeg's drawtext), 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 background-replacement.py and run:
python3 background-replacement.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Background replacement: the product stays put while everything behind it dissolves into a new warm studio scene.
Usage: python3 background-replacement.py input.mp4 output.mp4
Needs ffmpeg, Pillow, numpy and rembg to cut the product out of every frame
(pip install pillow numpy "rembg[cpu]"; rembg downloads its u2net model on first use).
"""
import json
import subprocess
import sys
import numpy as np
from PIL import Image, ImageDraw, ImageFilter
from rembg import new_session
START = 1.0 # second the new background starts to dissolve in
FADE = 0.5 # seconds the dissolve takes
WALL = ((250, 150, 105), # wall color at the top...
(255, 205, 160)) # ...and where it meets the floor
SUN = (255, 238, 210) # soft disc on the wall behind the product
SUN_Y, SUN_R = 0.40, 0.42 # disc center (0-1 down the frame) and radius (share of the frame width)
FLOOR_Y = 0.78 # where the floor starts, 0-1 down the frame (at the product's base)
FLOOR = ((240, 175, 135), # floor color at the back...
(205, 130, 95)) # ...and at the front
SHADOW = 0.45 # darkness of the contact shadow under the product (0-1)
def gradient(top, bottom, rows):
p = np.linspace(0, 1, rows)[:, None]
return np.array(top) * (1 - p) + np.array(bottom) * p
def scene():
"""The new background: a wall gradient with a soft disc, over a floor that starts at FLOOR_Y."""
split = round(h * FLOOR_Y)
img = np.zeros((h, w, 3))
img[:split] = gradient(*WALL, split)[:, None]
img[split:] = gradient(*FLOOR, h - split)[:, None]
disc = Image.new("L", (w, h))
r = w * SUN_R
ImageDraw.Draw(disc).ellipse([w / 2 - r, h * SUN_Y - r, w / 2 + r, h * SUN_Y + r], fill=255)
disc = np.asarray(disc.filter(ImageFilter.GaussianBlur(w * 0.04)), np.float32)[:, :, None] / 255
disc[split:] = 0
return img * (1 - disc) + np.array(SUN) * disc
def shadow(subject):
"""A soft dark ellipse under the product's base, following it as the camera moves."""
ys, xs = np.nonzero(subject[:, :, 0] > 0.5)
layer = Image.new("L", (w, h))
if len(xs):
base, half = ys.max(), (xs.max() - xs.min()) * 0.6
cx = xs[ys > base - h * 0.03].mean()
ImageDraw.Draw(layer).ellipse([cx - half, base - h * 0.012, cx + half, base + h * 0.018], fill=255)
return np.asarray(layer.filter(ImageFilter.GaussianBlur(w * 0.025)), np.float32)[:, :, None] / 255 * SHADOW
def composite(rgb, t):
"""The new scene, dissolved in over FADE, with the product cut out of the frame on top."""
if t < START:
return rgb
subject = np.asarray(session.predict(Image.fromarray(rgb))[0], np.float32)[:, :, None] / 255
p = min((t - START) / FADE, 1)
p = p * p * (3 - 2 * p)
back = rgb * (1 - p) + backdrop * (1 - shadow(subject)) * p
return (back * (1 - subject) + rgb * subject).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, composite each one 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)
writer.stdin.write(composite(rgb, 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)
backdrop = scene()
session = new_session("u2net")
render(src, out)





