---
title: "How to add raindrops on glass with Claude (tested prompt and script)"
description: "Clear water drops spatter onto an invisible pane in front of the bottle, each showing a tiny upside-down view of the scene, then a few big ones trickle down leaving trails of beads. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/raindrops-on-glass
---

# How to add raindrops on glass with Claude

Clear water drops spatter onto an invisible pane in front of the bottle, each showing a tiny upside-down view of the scene, then a few big ones trickle down leaving trails of beads.

Made by the script on this page, from our own test clip.

## Get this effect

1. ### Switch to Code in your [Claude Desktop App](https://claude.com/download)

2. ### Install the skill (once)

   ```
   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 raindrops on glass to input.mp4
   Second the first drop lands: 0.5
   Seconds over which the drops land, one after another: 1.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 raindrops on glass 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                                                                 |
| --------------- | ----------- | ---------------------------------------------------------------------------- |
| `START`         | `0.5`       | Second the first drop lands.                                                 |
| `LANDING`       | `1.0`       | Seconds over which the drops land, one after another.                        |
| `COUNT`         | `220`       | Drops sitting on the glass.                                                  |
| `RADIUS`        | `(3, 15)`   | Radius of the smallest and biggest drops, in pixels.                         |
| `TRICKLE`       | `6`         | Big drops that slide down the glass.                                         |
| `TRICKLE_START` | `1.6`       | Second the first one starts to slide; the rest follow every 0.4 seconds.     |
| `SPEED`         | `(90, 160)` | Slide speed of the slowest and fastest trickling drop, in pixels per second. |
| `LENS`          | `2.2`       | How much of the scene each drop shows, flipped. Higher is a wider view.      |
| `SEED`          | `3`         | Change for a different arrangement of drops.                                 |

## Without the skill

Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.

```
Add raindrops on glass to input.mp4, as if the camera were behind a wet window. Leave the video unchanged before 0.5 seconds. From 0.5 to 1.5 seconds, 220 drops land one after another at random places, each spreading to full size over 0.08 seconds and then staying put. Their radii run from 3 to 15 pixels, mostly small, and each is 1.1 times taller than wide with a slightly lumpy outline. Draw every drop as a lens rather than a flat shape: inside it, show the picture around it flipped upside down and left to right, taken from an area 2.2 times the drop's size. Darken the upper rim of each drop to 46% at its edge, brighten its lower rim by up to 70 levels, and put a pure white (255, 255, 255) glint in its top-left, centered 35% of the radius left and 42% up from the middle with a radius of 20% of the drop's. Also place 6 big drops (radius 13 to 15 pixels), spread evenly across the width, starting between 5% and 35% down the frame. At 1.6 seconds the first starts sliding down at 90 to 160 pixels per second, with the rest following every 0.4 seconds; each wobbles 4 pixels side to side, stretches up to 1.4 times taller than wide, and leaves a line of small beads behind it, one every 14 pixels and 22 to 42% of its radius. Don't dim or tint the rest of the picture. Use Python with numpy to draw the drops and ffmpeg to read and write 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 `raindrops-on-glass.py` and run:

```
python3 raindrops-on-glass.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Raindrops on glass: water drops land on an invisible pane in front of the picture, each one a tiny upside-down lens, and a few trickle down.

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

import numpy as np

START = 0.5              # second the first drop lands
LANDING = 1.0            # seconds over which the drops land, one after another
COUNT = 220              # drops sitting on the glass
RADIUS = (3, 15)         # radius of the smallest and biggest drops, in pixels
TRICKLE = 6              # big drops that slide down the glass
TRICKLE_START = 1.6      # second the first one starts to slide (the rest follow every 0.4 seconds)
SPEED = (90, 160)        # slide speed of the slowest and fastest trickling drop, in pixels per second
LENS = 2.2               # how much of the scene each drop shows, flipped (higher = wider view)
SEED = 3                 # 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 drops_at(t):
    """(x, y, radius, height/width, lumpiness) of every drop on the glass at time t."""
    out = []
    for x, y, r, lands, lump in sitting:
        grow = (t - lands) / 0.08  # a drop spreads to full size in 0.08 seconds as it lands
        if grow > 0:
            out.append((x, y, r * min(grow, 1), 1.1, lump))
    for i, (x, y, r, speed) in enumerate(sliders):
        slide = max(t - (TRICKLE_START + 0.4 * i), 0)  # they sit from START like the rest, then let go
        y_now = y + speed * slide
        out.append((x + 4 * np.sin(y_now / 31), y_now, r, 1.1 + min(slide, 0.3), 0))  # stretches as it runs
        # it leaves a line of small beads behind it, one every 14 pixels
        for k, bead_y in enumerate(np.arange(y + 14, y_now - r * 1.4, 14)):
            out.append((x + 4 * np.sin(bead_y / 31), bead_y, r * (0.22 + 0.1 * (k % 3)), 1.2, 0))
    return out

def glass(img, t):
    """Draw every drop as a little lens: a flipped, wider view of what's around it, a shadowed top edge,
    a bright bottom edge where the light gathers, and a white glint."""
    src = img.astype(np.float32)
    res = src.copy()
    for cx, cy, r, tall, lump in drops_at(t):
        x0, x1 = int(max(cx - r * 1.2 - 1, 0)), int(min(cx + r * 1.2 + 2, w))
        y0, y1 = int(max(cy - r * tall * 1.2 - 1, 0)), int(min(cy + r * tall * 1.2 + 2, h))
        if x0 >= x1 or y0 >= y1 or r < 1:
            continue
        yy, xx = np.mgrid[y0:y1, x0:x1].astype(np.float32)
        dx, dy = (xx - cx) / r, (yy - cy) / (r * tall)
        angle = np.arctan2(dy, dx)
        d = np.hypot(dx, dy) / (1 + 0.12 * lump * np.sin(3 * angle + 7 * lump))  # real drops aren't perfect circles
        inside = np.clip((1 - d) * r, 0, 1)[..., None]  # 1 inside, a one-pixel soft edge
        sx = np.clip(cx - dx * r * LENS, 0, w - 1).astype(int)  # flipped: left shows right, top shows bottom
        sy = np.clip(cy - dy * r * tall * LENS, 0, h - 1).astype(int)
        rim = d[..., None] ** 4
        lens = src[sy, sx] * (1.06 - 0.6 * rim * (dy[..., None] < 0.2))  # shadow round the top of the rim
        lens += 70 * rim * np.clip(dy[..., None] * 2, 0, 1)                # light gathers at the bottom edge
        glint = np.clip(1 - np.hypot(dx + 0.35, dy + 0.42) / 0.2, 0, 1)[..., None] ** 0.5  # top-left glint
        lens = lens * (1 - glint) + 255 * glint
        res[y0:y1, x0:x1] = res[y0:y1, x0:x1] * (1 - inside) + lens * inside
    return res.clip(0, 255).astype(np.uint8)

src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
rng = np.random.default_rng(SEED)
size = rng.uniform(size=COUNT) ** 2  # mostly small drops, a few big ones
sitting = [(rng.uniform(0, w), rng.uniform(0, h), RADIUS[0] + (RADIUS[1] - RADIUS[0]) * s,
            START + rng.uniform(0, LANDING), rng.uniform()) for s in size]
sliders = [(w * (i + 0.5) / TRICKLE + rng.uniform(-20, 20), rng.uniform(0.05, 0.35) * h,
            RADIUS[1] * rng.uniform(0.85, 1.0), rng.uniform(*SPEED)) for i in range(TRICKLE)]

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)
    t = i / fps
    writer.stdin.write((glass(img, t) if t >= START else img).tobytes())
    i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
    sys.exit("ffmpeg failed")
```

## More overlays and light

- [Anamorphic streak **Anamorphic streak**](https://www.recharm.com/effects/anamorphic-streak)
- [Aurora **Aurora**](https://www.recharm.com/effects/aurora)
- [Blood splatter **Blood splatter**](https://www.recharm.com/effects/blood-splatter)
- [Bokeh lights **Bokeh lights**](https://www.recharm.com/effects/bokeh-lights)
- [Camera flash **Camera flash**](https://www.recharm.com/effects/camera-flash)

## 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](https://www.recharm.com/get-started)
