How to add a disintegration with Claude

At 1 second the shot freezes and the serum bottle crumbles from the dropper cap down into purple and gray dust that streams off to the upper right, leaving the acrylic block empty.

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 disintegration to input.mp4
    Second the shot freezes and the subject starts to crumble: 1.0
    Seconds the crumbling takes to run from the top of the subject to the bottom: 1.4

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 disintegration 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
START1.0Second the shot freezes and the subject starts to crumble.
SWEEP1.4Seconds the crumbling takes to run from the top of the subject to the bottom.
LIFE(1.0, 1.6)Shortest and longest time a speck of dust lasts before it has faded out, in seconds.
GRAIN2Size of a speck of dust in pixels.
WIND(260, -70)Dust speed in pixels a second (right, down); each speck gets 0.4x to 1.6x of it.
GUST220Extra push to the right in pixels a second, every second.
ASH(60, 52, 62)Ash color the edge darkens to and the dust is tinted with.
CHAR0.03Seconds the edge darkens to ash before it breaks off.
SEED7Change for a different scatter.

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 serum bottle in input.mp4 (540x960, 30fps, 6 seconds, the camera circling it) disintegrate into dust. Play the clip untouched until 1.0 seconds, then freeze on the frame at 1.0 seconds and hold that frozen frame to the end. Cut the bottle out of the frozen frame with rembg (u2net model). Make an empty backdrop by painting the bottle out: dilate the mask (alpha above 0.1) by 7 pixels each way, inpaint at half size with OpenCV's Telea inpainting (radius 9), scale back up, blur it (Gaussian sigma 6) and blend it in through the hole blurred with sigma 4. Give every bottle pixel a break-off time of 1.0 + 1.4 x (0.7 x d + 0.3 x n) seconds, where d runs from 0 at the top of the bottle to 1 at its bottom and n is blotchy 0-1 noise (80% random noise blurred with sigma 9 plus 20% blurred with sigma 1.5, each stretched to 0-1), so it crumbles from the cap down in ragged clumps. Until its time a pixel shows the frozen bottle (darkening 70% toward ash (60, 52, 62) over its last 0.03 seconds); after it, the empty backdrop. Each 2x2 pixel cell of the bottle becomes a 2x2 speck of dust at its break-off time, colored 65% its own color and 35% ash, moving right at 260 and up at 70 pixels a second times a random 0.4 to 1.6 (plus random up/down drift with a 40 px/s spread), with an extra 220 px/s² push to the right and a 6 pixel side-to-side wobble, fading out (alpha (1 - age/life)^1.5) over a random 1.0 to 1.6 seconds, so all dust is gone by 4.1 seconds. There is no text, so nothing needs drawing with Pillow or ffmpeg's drawtext. Use Python with numpy, OpenCV and rembg, and ffmpeg to read and write the video; 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 disintegration.py and run:

python3 disintegration.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Disintegration: the shot freezes and the product crumbles from the top down into dust that blows away.

Usage: python3 disintegration.py input.mp4 output.mp4
Needs ffmpeg, numpy, Pillow, OpenCV and rembg to cut the product out of the frozen frame
(pip install numpy pillow opencv-python-headless "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

START = 1.0              # second the shot freezes and the subject starts to crumble
SWEEP = 1.4              # seconds the crumbling takes to run from the top of the subject to the bottom
LIFE = (1.0, 1.6)        # seconds each speck of dust lasts before it has faded out (shortest, longest)
GRAIN = 2                # size of a speck of dust in pixels
WIND = (260, -70)        # dust speed in pixels a second (right, down), up to 1.6x for the fastest specks
GUST = 220               # extra push to the right in pixels a second, every second
ASH = (60, 52, 62)       # dark ash color the edge turns just before it breaks off
CHAR = 0.03              # seconds the edge darkens to ash before it breaks off
SEED = 7                 # change for a different scatter


def plate(still, mask):
    """The frozen frame with the subject painted out, so the backdrop shows where it stood."""
    hole = cv2.dilate((mask > 0.1).astype(np.uint8), np.ones((15, 15), np.uint8))
    small = cv2.resize(still, (w // 2, h // 2), interpolation=cv2.INTER_AREA).astype(np.uint8)
    small_hole = cv2.resize(hole, (w // 2, h // 2), interpolation=cv2.INTER_NEAREST)
    filled = cv2.inpaint(small, small_hole, 9, cv2.INPAINT_TELEA)
    filled = cv2.GaussianBlur(cv2.resize(filled, (w, h), interpolation=cv2.INTER_CUBIC), (0, 0), 6)
    soft = cv2.GaussianBlur(hole.astype(np.float32), (0, 0), 4)[:, :, None]
    return still * (1 - soft) + filled * soft


def break_times(mask):
    """Second each pixel breaks off: top first, with a blotchy edge so it goes in clumps."""
    ys, xs = np.nonzero(mask > 0.5)
    top, bottom = ys.min(), ys.max()
    blotch = sum(k * norm(cv2.GaussianBlur(rng.random((h, w)).astype(np.float32), (0, 0), size))
                 for k, size in ((0.8, 9), (0.2, 1.5)))
    down = np.clip((np.arange(h)[:, None] - top) / (bottom - top), 0, 1)
    return START + SWEEP * (0.7 * down + 0.3 * blotch)


def norm(a):
    """Stretch an array to run from 0 to 1."""
    return (a - a.min()) / (a.max() - a.min())


def dust(still, mask, when):
    """One speck per GRAIN x GRAIN cell of the subject: where it starts, when it goes, how it moves, its color."""
    ys, xs = np.nonzero(mask[::GRAIN, ::GRAIN] > 0.5)
    ys, xs = ys * GRAIN, xs * GRAIN
    n = len(xs)
    speed = rng.uniform(0.4, 1.6, n)
    return {"x": xs.astype(np.float32), "y": ys.astype(np.float32), "t": when[ys, xs],
            "vx": WIND[0] * speed, "vy": WIND[1] * speed + rng.normal(0, 40, n),
            "life": rng.uniform(*LIFE, n), "phase": rng.uniform(0, 6.3, n),
            "color": still[ys, xs] * 0.65 + np.array(ASH) * 0.35}


def freeze(rgb):
    """Everything the crumbling needs from the frozen frame: the subject's mask, the empty backdrop, the dust."""
    still = rgb.astype(np.float32)
    mask = np.asarray(session.predict(Image.fromarray(rgb))[0], np.float32) / 255
    when = break_times(mask)
    return {"still": still, "mask": mask, "when": when, "backdrop": plate(still, mask), "dust": dust(still, mask, when)}


def frame_at(scene, t):
    """The frozen frame: subject still standing where it hasn't broken off, ashen at the edge, dust on top."""
    still, mask, when, d = scene["still"], scene["mask"], scene["when"], scene["dust"]
    left = (mask * (when > t))[:, :, None]
    char = np.clip(1 - (when - t) / CHAR, 0, 1)[:, :, None] * 0.7
    img = scene["backdrop"] * (1 - left) + (still * (1 - char) + np.array(ASH) * char) * left
    age = t - d["t"]
    live = (age > 0) & (age < d["life"])
    a = age[live]
    x = d["x"][live] + d["vx"][live] * a + 0.5 * GUST * a * a + 6 * np.sin(5 * a + d["phase"][live])
    y = d["y"][live] + d["vy"][live] * a
    alpha = (1 - a / d["life"][live])[:, None] ** 1.5
    on = (x >= 0) & (x < w - GRAIN) & (y >= 0) & (y < h - GRAIN)
    x, y, alpha, color = x[on].astype(int), y[on].astype(int), alpha[on], d["color"][live][on]
    for dy in range(GRAIN):
        for dx in range(GRAIN):
            img[y + dy, x + dx] = img[y + dy, x + dx] * (1 - alpha) + color * alpha
    return img.clip(0, 255).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):
    """Pass frames through until START, then draw the frozen, crumbling frame to the end, 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, scene = 0, None
    while raw := reader.stdout.read(w * h * 3):
        rgb = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
        if i / fps >= START:
            scene = scene or freeze(rgb)
            rgb = frame_at(scene, i / fps)
        writer.stdin.write(rgb.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)
rng = np.random.default_rng(SEED)
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