How to add a background blur with Claude

Half a second in, the holographic backdrop and the acrylic block under the bottle melt into a soft, creamy blur while the purple serum bottle stays crisp down to the small print on its label.

Get this effect

  1. Switch to Code in your Claude Desktop App

  2. 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/skills
  3. Ask for the effect

    Add a background blur to input.mp4
    Background blur (Gaussian sigma) as a share of the frame width: 16 px on a 540-wide video: 0.03
    Second the background starts to go soft: 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 background blur 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.

SettingDefaultWhat it does
BLUR0.03Background blur (Gaussian sigma) as a share of the frame width: 16 px on a 540-wide video.
START0.5Second the background starts to go soft.
EASE0.5Seconds it takes to reach full blur.
EDGE1.5Softness of the cut-out's edge in pixels.
MASK_EVERY2Cut the product out on every Nth frame and reuse it in between.

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
Blur the background of input.mp4 (540x960, the camera circles a purple serum bottle standing on an acrylic block) like a portrait-mode lens, keeping the bottle sharp. Leave the video untouched before 0.5 seconds. From 0.5 seconds, cut the bottle (with its dropper cap) out of every frame with rembg (u2net model), or every second frame and reuse the cut-out in between, and soften the cut-out's edge with a 1.5 px Gaussian blur. Paint the bottle out of the frame first by filling its area from the surrounding backdrop, so the blur doesn't spread a purple halo, then Gaussian-blur that plate; the blur's sigma eases from 0 to 16 px (3% of the frame width) between 0.5 and 1.0 seconds with a smoothstep curve and stays at 16 px to the end. Everything that isn't the bottle goes soft, the acrylic block included. Put the untouched bottle back on top through its cut-out. Use Python with OpenCV and numpy for the blur and 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-blur.py and run:

python3 background-blur.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Background blur: everything behind the product melts into a soft, creamy blur while the product itself stays pin sharp.

Usage: python3 background-blur.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.03      # background blur (Gaussian sigma) as a share of the frame width: 16 px on a 540-wide video
START = 0.5      # second the background starts to go soft
EASE = 0.5       # seconds it takes to reach full blur
EDGE = 1.5       # softness of the cut-out's edge in pixels
MASK_EVERY = 2   # 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 amount(t):
    """0 before START, easing to 1 over EASE seconds."""
    p = min(max((t - START) / EASE, 0), 1)
    return p * p * (3 - 2 * p)


def cutout(rgb):
    """The product's matte, 0-1, with a slightly softened edge."""
    return blur(np.asarray(session.predict(Image.fromarray(rgb))[0], np.float32) / 255, EDGE)


def portrait(rgb, mask, t):
    """Blur the backdrop with the product painted out of it, then put the sharp product back on top."""
    img = rgb.astype(np.float32)
    # fill the product's hole from the backdrop around it (worked out at quarter size, it's only a rough fill),
    # so the blur doesn't smear a halo of the product's colors
    keep = 1 - cv2.dilate(mask, np.ones((9, 9), np.uint8))
    small = cv2.resize(np.dstack([img * keep[:, :, None], keep]), (w // 4, h // 4), interpolation=cv2.INTER_AREA)
    small = blur(small, w * 0.0125)
    fill = cv2.resize(small[:, :, :3] / np.maximum(small[:, :, 3:], 1e-3), (w, h))
    backdrop = blur(img * keep[:, :, None] + fill * (1 - keep[:, :, None]), BLUR * w * amount(t))
    m = mask[:, :, None]
    return (img * m + backdrop * (1 - m)).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, blur each one's background 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, mask = 0, None
    while raw := reader.stdout.read(w * h * 3):
        rgb = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
        t = i / fps
        if t >= START and (mask is None or i % MASK_EVERY == 0):
            mask = cutout(rgb)
        writer.stdin.write((rgb if t < START else portrait(rgb, mask, t)).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)

Find 3x winning ads

Recharm cuts your ad footage into tagged, searchable clips: hooks, testimonials, product shots. Find the shots that win, add the effect with Claude, and ship 3x more ad edits.

Start 14-day free trial