How to add a shatter with Claude

The picture freezes, white cracks race out from just above the center like struck glass, and the shards tumble down into black before the video fades back in.

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 shatter to input.mp4
    Second the glass is struck and the frame freezes: 1.5
    Second the shards start to fall: 2.0

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 shatter 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
IMPACT1.5Second the glass is struck and the frame freezes.
FALL2.0Second the shards start to fall.
REVEAL, FADE2.6, 0.6When the video starts fading back up behind the shards, and how long that takes.
X, Y0.5, 0.45Impact point, as a share of the frame across and down.
RINGS, PER_RING6, 9How many shards: rings around the impact point, and shards in each ring.
CRACK_COLOR(255, 255, 255)Color of the crack lines.
SEED3Change it for a different crack pattern.

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 glass shatter effect to input.mp4. Play it normally until 1.5 seconds, then freeze that frame and crack it: split the frame into about 55 Voronoi shards from seeds placed in 6 jittered rings around an impact point 50% across and 45% down (small shards near the point, bigger toward the edges), and draw the shard borders as pure white (255, 255, 255) lines 3 pixels wide, growing outward from the impact point so they cover the whole frame by 1.8 seconds. At 2.0 seconds the shards fall away: each one drifts outward from the impact point at 80 to 220 pixels per second, spins up to 120 degrees per second and falls under gravity of 2600 pixels per second squared, with shards farther from the point starting up to 0.35 seconds later. Behind them is black until 2.6 seconds, then the live video (not the frozen frame) fades up over 0.6 seconds and plays normally to the end. Keep the size, 30fps, the 6-second length and the audio. Use Python with numpy and Pillow for the shards and ffmpeg to read and write the video (don't rely on ffmpeg's drawtext filter), and save it as output.mp4.

Run it without Claude

This is the exact script we tested. Save it as shatter.py and run:

python3 shatter.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Shatter: the frame freezes, white cracks spread from a point like struck glass, then the shards fall away into black and the video fades back up.

Usage: python3 shatter.py input.mp4 output.mp4
Needs ffmpeg, numpy and Pillow (pip install numpy pillow).
"""
import json
import subprocess
import sys

import numpy as np
from PIL import Image

IMPACT = 1.5             # second the glass is struck (the frame freezes here)
CRACK = 0.3              # seconds the cracks take to spread across the frame
FALL = 2.0               # second the shards start to fall
REVEAL = 2.6             # second the video starts fading back up behind the shards
FADE = 0.6               # seconds that fade takes
X, Y = 0.5, 0.45         # impact point (0-1 across and down the frame)
RINGS, PER_RING = 6, 9   # shards: rings around the impact point, and shards per ring
CRACK_WIDTH = 3          # crack line width in pixels
CRACK_COLOR = (255, 255, 255)
GRAVITY = 2600           # pixels per second squared
FPS = 30                 # output frame rate
SEED = 3                 # change for a different crack pattern


def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height:format=duration", "-of", "json", path]))
    return info["streams"][0]["width"], info["streams"][0]["height"], float(info["format"]["duration"])


def frames(path):
    ff = subprocess.Popen(["ffmpeg", "-v", "error", "-i", path, "-vf", f"fps={FPS}",
                           "-f", "rawvideo", "-pix_fmt", "rgb24", "-"], stdout=subprocess.PIPE)
    while True:
        data = ff.stdout.read(w * h * 3)
        if len(data) < w * h * 3:
            return
        yield np.frombuffer(data, np.uint8).reshape(h, w, 3)


def shard_map():
    """Label every pixel with its shard: a Voronoi pattern from seeds in jittered rings around the impact,
    small near the point and larger toward the edges, like a struck pane."""
    seeds = [(cx, cy)]
    reach = np.hypot(w, h)
    for ring in range(1, RINGS + 1):
        radius = reach * (ring / RINGS) ** 1.6 * 0.75
        for k in range(PER_RING):
            a = (k + rng.uniform(-0.35, 0.35) + ring * 0.5) / PER_RING * 2 * np.pi
            r = radius * rng.uniform(0.8, 1.15)
            seeds.append((cx + r * np.cos(a), cy + r * np.sin(a)))
    best, labels = np.full((h, w), np.inf), np.zeros((h, w), int)
    for i, (sx, sy) in enumerate(seeds):
        d = np.hypot(xs - sx, ys - sy)
        labels[d < best] = i
        best = np.minimum(best, d)
    return labels


def crack_lines():
    """Pixels on a shard border, thickened to CRACK_WIDTH."""
    edge = np.zeros((h, w), bool)
    edge[:, 1:] |= labels[:, 1:] != labels[:, :-1]
    edge[1:, :] |= labels[1:, :] != labels[:-1, :]
    thick = edge.copy()
    for d in range(1, CRACK_WIDTH):
        thick[d:, :] |= edge[:-d, :]
        thick[:, d:] |= edge[:, :-d]
    return thick


def cracked(still, t):
    """The frozen frame with the cracks drawn out to a radius that grows from the impact point."""
    radius = np.hypot(w, h) * min((t - IMPACT) / CRACK, 1)
    img = still.copy()
    img[lines & (from_impact <= radius)] = CRACK_COLOR
    return img


def cut_shards(glass):
    """Each shard as an RGBA cutout with its center and its own drift, spin and start time."""
    shards = []
    for i in range(labels.max() + 1):
        ys, xs = np.nonzero(labels == i)
        if not len(xs):
            continue
        x0, x1, y0, y1 = xs.min(), xs.max() + 1, ys.min(), ys.max() + 1
        rgba = np.dstack([glass[y0:y1, x0:x1], (labels[y0:y1, x0:x1] == i) * np.uint8(255)])
        mx, my = xs.mean(), ys.mean()
        away = np.array([mx - cx, my - cy])
        away = away / max(np.hypot(*away), 1)
        shards.append({"img": Image.fromarray(rgba), "center": (mx, my),
                       "push": away * rng.uniform(80, 220), "spin": rng.uniform(-120, 120),
                       "start": FALL + 0.35 * np.hypot(mx - cx, my - cy) / np.hypot(w, h) + rng.uniform(0, 0.08)})
    return shards


def falling(live, t):
    """Shards still in place or falling, over black that fades up to the live video."""
    level = np.clip((t - REVEAL) / FADE, 0, 1)
    img = Image.fromarray((live * level).astype(np.uint8))
    for s in shards:
        dt = max(t - s["start"], 0)
        x = s["center"][0] + s["push"][0] * dt
        y = s["center"][1] + s["push"][1] * dt + 0.5 * GRAVITY * dt * dt
        piece = s["img"].rotate(s["spin"] * dt, resample=Image.BICUBIC, expand=True) if dt else s["img"]
        left, top = round(x - piece.width / 2), round(y - piece.height / 2)
        if top < h and top + piece.height > 0 and left < w and left + piece.width > 0:
            img.paste(piece, (left, top), piece)
    return np.asarray(img)


src, out = sys.argv[1:3]
w, h, length = probe(src)
rng = np.random.default_rng(SEED)
cx, cy = w * X, h * Y
ys, xs = np.mgrid[0:h, 0:w]
labels = shard_map()
lines = crack_lines()
from_impact = np.hypot(xs - cx, ys - cy)

enc = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
                        "-r", str(FPS), "-i", "-", "-i", src, "-map", "0:v", "-map", "1:a?",
                        "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "copy", out],
                       stdin=subprocess.PIPE)
still, shards = None, None
for i, frame in enumerate(frames(src)):
    t = i / FPS
    if t < IMPACT:
        result = frame
    elif t < FALL:
        still = frame if still is None else still
        result = cracked(still, t)
    else:
        shards = shards or cut_shards(cracked(still, FALL))
        result = falling(frame, t)
    enc.stdin.write(np.ascontiguousarray(result).tobytes())
enc.stdin.close()
if enc.wait():
    sys.exit("ffmpeg failed")

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