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 floating product to input.mp4 Where the product's base sits in the input, 0-1 down the frame; anything below it (the stand) is dropped: 0.775 How far the product floats above its spot, as a share of the frame height: 0.09
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 floating product 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 |
|---|---|---|
BASE | 0.775 | Where the product's base sits in the input, 0-1 down the frame; anything below it (the stand) is dropped. |
LIFT | 0.09 | How far the product floats above its spot, as a share of the frame height. |
BOB | 0.025 | How far it bobs up and down from there, as a share of the frame height. |
PERIOD | 2.0 | Seconds for one bob up and back down. |
TOP, BOTTOM | (242, 236, 252), (212, 200, 236) | Backdrop gradient colors at the top and bottom of the frame. |
SHADOW_Y | 0.79 | Shadow center, 0-1 down the frame. |
SHADOW_W | 0.36 | Shadow width at the product's lowest point, as a share of the frame width. |
DARKEST, LIGHTEST | 0.65, 0.35 | Shadow opacity at the product's lowest and highest points. |
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
Make the product in input.mp4 (540x960, 30fps, the camera circles a serum bottle standing on an acrylic block, its base 77.5% down the frame) float. Replace the whole background with a vertical gradient from RGB (242, 236, 252) at the top to (212, 200, 236) at the bottom. On every frame, cut the bottle out with rembg (u2net model) and drop the cut-out below 77.5% down (744 px) with a 4-pixel fade, so the block doesn't come with it. Move the cut-out straight up by 0.09 + 0.025 * sin(2 * pi * t / 2) of the frame height (rounded to whole pixels: 110 px at 0.5s, 62 px at 1.5s), so it bobs once every 2 seconds. Under it draw a soft shadow: an ellipse in RGB (70, 45, 100), centered horizontally at 79% down, 36% of the frame width across and 0.12 times as tall as it is wide when the bottle is lowest, shrinking to 80% of that width when it is highest, blurred with an 11.5-pixel Gaussian blur, at 65% opacity at the lowest point fading to 35% at the highest (blend linearly with the height). Layer the backdrop, then the shadow, then the moved bottle. Use Python with Pillow and numpy and ffmpeg to read and write the video, keep the size, 30fps, 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 floating-product.py and run:
python3 floating-product.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Floating product: the cut-out product hovers over a clean lilac backdrop, bobbing gently above its soft shadow.
Usage: python3 floating-product.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 math
import subprocess
import sys
import numpy as np
from PIL import Image, ImageDraw, ImageFilter
from rembg import new_session
BASE = 0.775 # where the product's base sits in the input, 0-1 down; anything below (its stand) is dropped
LIFT = 0.09 # how far the product floats above its spot, as a share of the frame height
BOB = 0.025 # bob up and down from there, as a share of the frame height
PERIOD = 2.0 # seconds for one bob up and back down
TOP = (242, 236, 252) # backdrop color at the top of the frame
BOTTOM = (212, 200, 236) # backdrop color at the bottom
SHADOW_Y = 0.79 # shadow center, 0-1 down the frame (just under where the product stood)
SHADOW_W = 0.36 # shadow width at the product's lowest point, as a share of the frame width
SHADOW = (70, 45, 100) # shadow color
DARKEST, LIGHTEST = 0.65, 0.35 # shadow opacity at the product's lowest and highest points
def backdrop():
"""A vertical gradient from TOP to BOTTOM."""
k = np.linspace(0, 1, h, dtype=np.float32)[:, None, None]
return np.broadcast_to(np.array(TOP, np.float32) * (1 - k) + np.array(BOTTOM, np.float32) * k, (h, w, 3))
def height_at(t):
"""How high the product floats at time t (0 = its lowest, 1 = its highest)."""
return (1 + math.sin(2 * math.pi * t / PERIOD)) / 2
def shadow(up):
"""A soft ellipse that shrinks and fades as the product rises; returns its opacity per pixel."""
sw = w * SHADOW_W * (1 - 0.2 * up)
sh = sw * 0.12
img = Image.new("L", (w, h))
ImageDraw.Draw(img).ellipse([w / 2 - sw / 2, h * SHADOW_Y - sh / 2, w / 2 + sw / 2, h * SHADOW_Y + sh / 2], fill=255)
img = img.filter(ImageFilter.GaussianBlur(h * 0.012))
opacity = DARKEST + (LIGHTEST - DARKEST) * up
return np.asarray(img, np.float32)[:, :, None] / 255 * opacity
def composite(rgb, t):
"""Backdrop, then the shadow, then the product cut out of the frame and moved up."""
up = height_at(t)
shift = round(h * (LIFT + BOB * (2 * up - 1)))
cut = np.asarray(session.predict(Image.fromarray(rgb))[0], np.float32)[:, :, None] / 255 * above_base
a = shadow(up)
frame = base * (1 - a) + np.array(SHADOW, np.float32) * a
product, alpha = np.zeros_like(frame), np.zeros_like(cut)
product[:h - shift], alpha[:h - shift] = rgb[shift:], cut[shift:]
frame = frame * (1 - alpha) + product * alpha
return frame.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)
base = backdrop()
above_base = np.clip((h * BASE - np.arange(h, dtype=np.float32)) / 4, 0, 1)[:, None, None]
session = new_session("u2net")
render(src, out)





