---
title: "How to add a blood splatter with Claude (tested prompt and script)"
description: "A dark red splat smacks onto the lens top left, a second hits lower right, spraying droplets, and thin blood drips crawl down the glass. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/blood-splatter
---

# How to add a blood splatter with Claude

A dark red splat smacks onto the lens top left, a second hits lower right, spraying droplets, and thin blood drips crawl down the glass.

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 blood splatter to input.mp4
   Each splat's hit time in seconds, center across and down (0-1), and radius as a share of the frame width: (1.0, 0.30, 0.28, 0.20), (1.6, 0.70, 0.60, 0.14)
   Seconds a splat takes to spread to full size: 0.12
   ```

## 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 blood splatter 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                                                                                              |
| ------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `SPLATS`      | `(1.0, 0.30, 0.28, 0.20), (1.6, 0.70, 0.60, 0.14)` | Each splat's hit time in seconds, center across and down (0-1), and radius as a share of the frame width. |
| `HIT`         | `0.12`                                             | Seconds a splat takes to spread to full size.                                                             |
| `DRIPS`       | `3`                                                | Drips running down from each splat.                                                                       |
| `DRIP_DELAY`  | `0.3`                                              | Seconds after the hit before the drips start to run.                                                      |
| `DRIP_LENGTH` | `(0.12, 0.30)`                                     | Shortest and longest drip, as a share of the frame height.                                                |
| `DRIP_TIME`   | `2.5`                                              | Seconds a drip takes to reach its full length.                                                            |
| `DARK, LIGHT` | `(115, 0, 10), (185, 18, 30)`                      | Blood color where it's thick and at the thin edges.                                                       |
| `OPACITY`     | `0.95`                                             | 1 is solid.                                                                                               |
| `SEED`        | `7`                                                | Change for different splat shapes.                                                                        |

## Without the skill

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

```
Add a blood splatter to input.mp4, as if blood hit the camera lens. Draw two splats, each a lumpy round body (about 14 overlapping circles of 30-55% of the splat radius, within 40% of the radius from its center), 16 tapered spikes reaching 0.8 to 1.5 radii out with a small drop at each tip, and about 35 loose droplets 1.1 to 2.4 radii out. The first splat is centered at 30% across and 28% down with a radius of 20% of the frame width and hits at 1.0 seconds; the second is centered at 70% across and 60% down with a radius of 14% of the frame width and hits at 1.6 seconds. Each splat spreads from 35% to full size over 0.12 seconds with an ease-out, then stays. 0.3 to 0.7 seconds after each hit, three drips run straight down from just below the splat's center, spaced 35% of the radius apart: rounded streaks 7-12% of the radius wide with a slightly fatter bead at the bottom, each growing to between 12% and 30% of the frame height over 2.5 seconds, slowing as it goes. Color the blood RGB (185, 18, 30) at the thin edges shading to (115, 0, 10) where it's thick, at 95% opacity. Nothing appears before 1.0 seconds and the rest of the picture stays untouched. Use Python with Pillow and numpy to draw the overlay for each frame and ffmpeg to put it on the video (don't use ffmpeg's drawtext filter), keep the size, frame rate and audio, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `blood-splatter.py` and run:

```
python3 blood-splatter.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Blood splatter: two dark red splats hit the lens one after another, spray droplets outward, then drip down the glass.

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

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

SPLATS = [               # (second it hits, center across, center down, radius as a share of the frame width)
    (1.0, 0.30, 0.28, 0.20),
    (1.6, 0.70, 0.60, 0.14),
]
HIT = 0.12               # seconds a splat takes to spread to full size
DRIPS = 3                # drips running down from each splat
DRIP_DELAY = 0.3         # seconds after the hit before the drips start to run
DRIP_LENGTH = (0.12, 0.30)  # shortest and longest drip, as a share of the frame height
DRIP_TIME = 2.5          # seconds a drip takes to reach its full length
DARK = (115, 0, 10)      # color in the thick middle of the blood
LIGHT = (185, 18, 30)    # color at the thin edges
OPACITY = 0.95           # 1 = solid
SEED = 7                 # change for different splat shapes

def splat_mask(r, rng):
    """A splat's shape: a lumpy body, tapered spikes ending in drops, and loose droplets around it."""
    s = math.ceil(r * 2.6)
    img = Image.new("L", (2 * s, 2 * s))
    d = ImageDraw.Draw(img)

    def dot(x, y, rad):
        d.ellipse([s + x - rad, s + y - rad, s + x + rad, s + y + rad], fill=255)

    for _ in range(14):
        a, dist = rng.uniform(0, 2 * math.pi), rng.uniform(0, 0.4) * r
        dot(dist * math.cos(a), dist * math.sin(a), rng.uniform(0.3, 0.55) * r)
    for _ in range(16):
        a, length, width = rng.uniform(0, 2 * math.pi), rng.uniform(0.8, 1.5) * r, rng.uniform(0.05, 0.12) * r
        side = (-math.sin(a) * width, math.cos(a) * width)
        tip = (length * math.cos(a), length * math.sin(a))
        d.polygon([(s + side[0], s + side[1]), (s - side[0], s - side[1]), (s + tip[0], s + tip[1])], fill=255)
        dot(tip[0], tip[1], width * rng.uniform(0.7, 1.2))
    for _ in range(35):
        a, dist = rng.uniform(0, 2 * math.pi), rng.uniform(1.1, 2.4) * r
        dot(dist * math.cos(a), dist * math.sin(a), rng.uniform(0.015, 0.06) * r)
    # soften and re-threshold so the overlapping circles melt into one organic edge
    return img.filter(ImageFilter.GaussianBlur(r * 0.04)).point(lambda v: 255 if v > 110 else 0)

def drip_paths(rng):
    """For each splat and drip: (start second, x, y, final length, width), in pixels."""
    paths = []
    for t, x, y, r in SPLATS:
        for i in range(DRIPS):
            dx = (i - (DRIPS - 1) / 2) * 0.35 * r * w + rng.uniform(-0.05, 0.05) * r * w
            paths.append((t + DRIP_DELAY + rng.uniform(0, 0.4), x * w + dx, y * h + 0.25 * r * w,
                          rng.uniform(*DRIP_LENGTH) * h, rng.uniform(0.035, 0.06) * r * w))
    return paths

def overlay(t):
    mask = Image.new("L", (w, h))
    for (start, x, y, _), shape in zip(SPLATS, shapes):
        if t < start:
            continue
        p = min((t - start) / HIT, 1)
        scale = 0.35 + 0.65 * (1 - (1 - p) ** 3)
        size = max(1, round(shape.width * scale))
        mask.paste(255, (round(x * w - size / 2), round(y * h - size / 2)), shape.resize((size, size), Image.BILINEAR))
    d = ImageDraw.Draw(mask)
    for start, x, y, length, width in drips:
        if t < start:
            continue
        p = min((t - start) / DRIP_TIME, 1)
        bottom = y + length * (1 - (1 - p) ** 2)
        d.rounded_rectangle([x - width, y, x + width, bottom], radius=width, fill=255)
        d.ellipse([x - 1.5 * width, bottom - 2.5 * width, x + 1.5 * width, bottom + 0.4 * width], fill=255)
    # thick blood reads darker: shade by how deep inside the shape each pixel is
    a = np.asarray(mask, np.float32) / 255
    depth = np.asarray(mask.filter(ImageFilter.GaussianBlur(8)), np.float32)[..., None] / 255
    rgb = np.array(LIGHT, np.float32) * (1 - depth) + np.array(DARK, np.float32) * depth
    return np.dstack([rgb, a * OPACITY * 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:format=duration", "-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), float(info["format"]["duration"])

def render(src, out):
    """Draw overlay(t) on every frame of src and save to out, keeping the audio."""
    ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", src,
                           "-f", "rawvideo", "-pix_fmt", "rgba", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
                           "-filter_complex", "[0:v][1:v]overlay=shortest=1,format=yuv420p[v]",
                           "-map", "[v]", "-map", "0:a?", "-c:v", "libx264", "-crf", "18", "-c:a", "copy", out],
                          stdin=subprocess.PIPE)
    for i in range(math.ceil(duration * fps)):
        ff.stdin.write(overlay(i / fps).tobytes())
    ff.stdin.close()
    if ff.wait():
        sys.exit("ffmpeg failed")

src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
rng = random.Random(SEED)
shapes = [splat_mask(r * w, rng) for _, _, _, r in SPLATS]
drips = drip_paths(rng)
render(src, out)
```

## More overlays and light

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

## 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)
