---
title: "How to add a rain with Claude (tested prompt and script)"
description: "The picture dims as thin blue-white streaks of rain start pouring in from the top, slanting down to the right. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/rain
---

# How to add a rain with Claude

The picture dims as thin blue-white streaks of rain start pouring in from the top, slanting down to the right.

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 a rain to input.mp4
   Second the first drops enter at the top: 0.5
   Number of streaks once the rain is going: 220
   ```

## 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 rain 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 drops enter at the top.                            |
| `COUNT` | `220`             | Number of streaks once the rain is going.                           |
| `ANGLE` | `15`              | Slant in degrees from vertical.                                     |
| `SPEED` | `(900, 1600)`     | Fall speed of the farthest and nearest drops, in pixels per second. |
| `COLOR` | `(240, 246, 255)` | Streak color.                                                       |
| `DIM`   | `0.72`            | The picture's brightness under the rain (1 = unchanged).            |

## Without the skill

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

```
Add rain to input.mp4. From 0.5 seconds, dim the picture to 72% brightness and draw 220 thin streaks of rain in blue-white (240, 246, 255), slanted 15 degrees from vertical so they fall down to the right. Give each drop a random depth: far drops are 30 pixels long, 1 pixel wide, 35% opaque and fall at 900 pixels per second; near drops are 90 pixels long, 2 pixels wide, 95% opaque and fall at 1600 pixels per second, with most drops far away. The first drops enter at the top edge at 0.5 seconds, so the rain pours in rather than appearing all at once, and each drop starts again above the frame when it falls off the bottom. Soften the streaks with a 0.5 pixel blur. Before 0.5 seconds the video is unchanged. Use Python with numpy and Pillow to draw the streaks 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 `rain.py` and run:

```
python3 rain.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Rain: thin blue-white streaks of rain start falling at 0.5 seconds, slanting down to the right across the dimmed picture.

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

import numpy as np
from PIL import Image, ImageDraw, ImageFilter

START = 0.5                # second the first drops enter at the top
COUNT = 220                # number of streaks on screen once the rain is going
ANGLE = 15                 # slant in degrees from vertical, falling down to the right
LENGTH = (30, 90)          # streak length of the farthest and nearest drops, in pixels
SPEED = (900, 1600)        # fall speed of the farthest and nearest drops, in pixels per second
OPACITY = (0.35, 0.95)     # opacity of the farthest and nearest drops
COLOR = (240, 246, 255)    # blue-white
DIM = 0.72                 # the picture's brightness under the rain (1 = unchanged)
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 rain(t):
    """The rain layer's opacity at time t (0-1), or None before it starts."""
    if t < START:
        return None
    layer = Image.new("L", (w, h))
    draw = ImageDraw.Draw(layer)
    for d in drops:
        fallen = d["speed"] * (t - START) - d["delay"]
        if fallen < 0:
            continue  # this drop hasn't reached the top of the frame yet
        y = fallen % cycle - d["length"]  # top end of the streak; wraps back above the frame
        x = (d["x"] + y * slant) % (w + margin) - margin / 2
        dx, dy = d["length"] * slant, d["length"]
        draw.line([(x, y), (x + dx, y + dy)], fill=round(255 * d["opacity"]), width=d["width"])
    return np.asarray(layer.filter(ImageFilter.GaussianBlur(0.5)), np.float32)[..., None] / 255

src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
slant = math.tan(math.radians(ANGLE))
cycle = h + LENGTH[1] * 2        # distance a drop falls before it starts again at the top
margin = h * slant + LENGTH[1]   # drops start left of the frame so the slant still covers it
rng = np.random.default_rng(SEED)
drops = []
for _ in range(COUNT):
    depth = rng.uniform() ** 1.5  # 0 = far, 1 = near; most drops are far away
    drops.append({"x": rng.uniform(0, w + margin), "delay": rng.uniform(0, cycle),
                  "speed": SPEED[0] + (SPEED[1] - SPEED[0]) * depth,
                  "length": LENGTH[0] + (LENGTH[1] - LENGTH[0]) * depth,
                  "opacity": OPACITY[0] + (OPACITY[1] - OPACITY[0]) * depth,
                  "width": 2 if depth > 0.6 else 1})
color = np.array(COLOR, np.float32)

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)
    alpha = rain(i / fps)
    if alpha is not None:
        img = img.astype(np.float32) * DIM  # an overcast dim so the streaks stand out
        img = (img * (1 - alpha) + color * alpha).astype(np.uint8)
    writer.stdin.write(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)
