How to add a snow with Claude

Round white snowflakes drift down in from the top edge and keep falling past the product, the big close ones dropping faster than the small far ones as they sway side to side.

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 snow to input.mp4
    Second the first flakes appear at the top edge: 0.3
    Number of flakes: 230

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 snow 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
START0.3Second the first flakes appear at the top edge.
COUNT230Number of flakes.
RADIUS(2.0, 7.5)Radius of the farthest and nearest flakes, in pixels.
SPEED(0.14, 0.42)Fall speed of the farthest and nearest flakes, in frame heights per second.
OPACITY(0.6, 1.0)How solid the farthest and nearest flakes are.
SHADE0.18Strength of the soft shadow round each flake.
SWAY12Side-to-side drift, in pixels.

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 falling snow to input.mp4. Use 230 round white (255, 255, 255) flakes, each given a random depth: far flakes have a 2 pixel radius, 60% opacity and fall at 0.14 frame heights per second, near flakes a 7.5 pixel radius, full opacity and 0.42 frame heights per second, with most flakes far away. Give each flake a solid middle with a soft edge and a faint soft shadow (18% darker at most, about 1.5 times the flake's radius) so it shows on a light background, and sway it side to side by 12 pixels over 2.5 to 5 seconds. Nothing shows before 0.3 seconds; then the flakes fall in from above the top edge (spread over one frame height above it, so the bottom of the frame only fills in after about 2 seconds), and each one wraps back to the top once it passes the bottom, so it keeps snowing to the end. Use Python with numpy to draw it and ffmpeg to put it on the video (don't rely on ffmpeg's drawtext filter), keep the audio, and save it as output.mp4.

Run it without Claude

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

python3 snow.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Snow: soft white snowflakes drift down from the top of the frame, big near ones falling fast and small far ones slowly.

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

import numpy as np

START = 0.3              # second the first flakes appear at the top edge
COUNT = 230              # number of flakes
RADIUS = (2.0, 7.5)      # radius of the farthest and nearest flakes, in pixels
SPEED = (0.14, 0.42)     # fall speed of the farthest and nearest flakes, in frame heights per second
OPACITY = (0.6, 1.0)     # how solid the farthest and nearest flakes are
SHADE = 0.18             # faint soft shadow round each flake, so it stands out on light backgrounds
SWAY = 12                # side-to-side drift, in pixels
SEED = 7                 # change for a different arrangement


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 snow(t):
    """How much white covers each pixel at time t, and how much its shadow darkens it (both 0-1), or None before the snow starts."""
    if t < START:
        return None
    white, shade = np.zeros((h, w), np.float32), np.zeros((h, w), np.float32)
    span = h + 2 * MARGIN
    for f in flakes:
        fallen = f["y"] + f["speed"] * h * (t - START)  # starts above the frame, so the snow falls in from the top
        if fallen < -MARGIN:
            continue
        y = (fallen + MARGIN) % span - MARGIN  # wraps back to the top once it passes the bottom
        x = (f["x"] + SWAY * np.sin(2 * np.pi * t / f["sway"] + f["phase"])) % w
        r = int(f["radius"] * 2.2) + 1
        x0, y0, x1, y1 = max(int(x) - r, 0), max(int(y) - r, 0), min(int(x) + r + 1, w), min(int(y) + r + 1, h)
        if x0 >= x1 or y0 >= y1:
            continue
        yy, xx = np.mgrid[y0:y1, x0:x1].astype(np.float32)
        d = np.sqrt((xx - x) ** 2 + (yy - y) ** 2) / f["radius"]
        flake = np.clip(1.6 - d, 0, 1) ** 1.5  # solid middle with a soft edge
        shadow = np.exp(-(d / 1.5) ** 2)       # a soft shadow a little wider than the flake
        white[y0:y1, x0:x1] = np.maximum(white[y0:y1, x0:x1], f["opacity"] * flake)
        shade[y0:y1, x0:x1] = np.maximum(shade[y0:y1, x0:x1], SHADE * f["opacity"] * shadow)
    return white[..., None], shade[..., None]


src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
MARGIN = RADIUS[1] * 3
rng = np.random.default_rng(SEED)
flakes = []
for _ in range(COUNT):
    depth = rng.uniform() ** 1.6  # 0 = far, 1 = near; most flakes are far away
    flakes.append({"x": rng.uniform(0, w),
                   "y": -MARGIN - rng.uniform(0, h + 2 * MARGIN),  # spread over one screen's height above the frame
                   "speed": SPEED[0] + (SPEED[1] - SPEED[0]) * depth * rng.uniform(0.85, 1.15),
                   "radius": RADIUS[0] + (RADIUS[1] - RADIUS[0]) * depth,
                   "opacity": OPACITY[0] + (OPACITY[1] - OPACITY[0]) * depth,
                   "sway": rng.uniform(2.5, 5), "phase": rng.uniform(0, 2 * np.pi)})

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", "16", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
                          stdin=subprocess.PIPE)
i = 0
while raw := reader.stdout.read(w * h * 3):
    img = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
    flakes_now = snow(i / fps)
    if flakes_now is not None:
        white, shade = flakes_now
        img = (img * (1 - shade) * (1 - white) + 255 * white).astype(np.uint8)  # shade a little, then mix toward white
    writer.stdin.write(img.tobytes())
    i += 1
writer.stdin.close()
if reader.wait() or writer.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