---
title: "How to add a rubber stamp text with Claude (tested prompt and script)"
description: "A red \"APPROVED\" stamp with a double border and patchy, worn-out ink slams down above the product at a tilt, knocking the picture with a small jolt. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/rubber-stamp-text
---

# How to add a rubber stamp text with Claude

A red "APPROVED" stamp with a double border and patchy, worn-out ink slams down above the product at a tilt, knocking the picture with a small jolt.

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 rubber stamp text to input.mp4
   Text: APPROVED
   ```

## 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 rubber stamp text 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                                                   |
| ------------- | --------------- | -------------------------------------------------------------- |
| `TEXT`        | `APPROVED`      | The word on the stamp.                                         |
| `LAND`        | `0.8`           | Second the stamp hits the frame.                               |
| `DROP`        | `0.12`          | Seconds it takes to come down onto the frame.                  |
| `START_SCALE` | `2.5`           | How big the stamp starts its drop, relative to its final size. |
| `WIDTH`       | `0.74`          | Stamp width, border included, as a share of the frame width.   |
| `X, Y`        | `0.5, 0.13`     | Stamp center (0-1 across and down the frame).                  |
| `INK`         | `(205, 30, 40)` | Ink color.                                                     |
| `OPACITY`     | `0.92`          | Ink opacity, so a little of the picture shows through.         |
| `TILT`        | `12`            | Rotation in degrees, counterclockwise.                         |
| `WEAR`        | `0.3`           | Share of the ink knocked out by the worn, patchy print.        |
| `JOLT`        | `10`            | How far the frame jumps on impact, in pixels.                  |
| `JOLT_LEN`    | `0.3`           | Seconds until the jolt settles.                                |

## Without the skill

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

```
Add a rubber stamp to input.mp4: the word "APPROVED" in a heavy font (Arial Black) in red ink (205, 30, 40) at 92% opacity, inside a rounded rectangle border, with a thick outer line (14% of the letter size) and a thin inner line (5% of the letter size). The stamp, border included, is 74% of the frame width wide and 1.7 times the letter size tall, with the word 80% of the stamp's width; tilt it 12 degrees counterclockwise and center it 50% across and 13% down. Make the ink look worn: mix soft blotchy noise with fine speckle (numpy is fine) and leave out the weakest 30% of the ink so the print is patchy. The stamp comes down over 0.12 seconds and hits the frame at 0.8 seconds: it starts at 250% size and fully transparent, shrinks to 100% while fading in, speeding up as it drops, then stays to the end. On impact, jolt the whole frame (stamp included) about 10 pixels in changing directions with an extra 4% zoom so no edge shows, both dying down to still over 0.3 seconds. Nothing changes before 0.68 seconds. Draw the stamp with Python and Pillow (don't rely on ffmpeg's drawtext filter), keep the size and audio, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `rubber-stamp-text.py` and run:

```
python3 rubber-stamp-text.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Rubber stamp text: a worn red "APPROVED" stamp slams down onto the frame, tilted, and the picture jolts.

Usage: python3 rubber-stamp-text.py input.mp4 output.mp4
Needs ffmpeg, Pillow and numpy (pip install pillow numpy).
"""
import json
import math
import subprocess
import sys
from pathlib import Path

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

TEXT = "APPROVED"
LAND = 0.8                # second the stamp hits the frame
DROP = 0.12               # seconds it takes to come down onto the frame
START_SCALE = 2.5         # how big the stamp starts its drop, relative to its final size
WIDTH = 0.74              # stamp width (border included) as a share of the frame width
X, Y = 0.5, 0.13          # stamp center (0-1 across and down the frame)
INK = (205, 30, 40)       # ink color
OPACITY = 0.92            # ink opacity, so a little of the picture shows through
TILT = 12                 # degrees, counterclockwise
WEAR = 0.3                # share of the ink knocked out by the worn, patchy print
JOLT = 10                 # how far the frame jumps on impact, in pixels
JOLT_LEN = 0.3            # seconds until the jolt settles
FONTS = ["/System/Library/Fonts/Supplemental/Arial Black.ttf",    # macOS
         "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",  # Linux
         "C:/Windows/Fonts/ariblk.ttf"]                           # Windows
FONT_FILE = next((f for f in FONTS if Path(f).exists()), None) or sys.exit("No bold font found: add one to FONTS")

def stamp():
    """The stamp drawn once: the word inside a thick and a thin border, worn patchy, tilted."""
    box_w = round(w * WIDTH)
    probe_font = ImageFont.truetype(FONT_FILE, 100)
    x0, y0, x1, y1 = probe_font.getbbox(TEXT)
    font = ImageFont.truetype(FONT_FILE, round(100 * box_w * 0.8 / (x1 - x0)))
    thick, thin = round(font.size * 0.14), round(font.size * 0.05)
    box_h = round(font.size * 1.7)

    ink = Image.new("L", (box_w, box_h))
    draw = ImageDraw.Draw(ink)
    draw.rounded_rectangle([0, 0, box_w - 1, box_h - 1], radius=thick * 2, outline=255, width=thick)
    gap = thick + thin * 2
    draw.rounded_rectangle([gap, gap, box_w - 1 - gap, box_h - 1 - gap], radius=thick, outline=255, width=thin)
    draw.text((box_w / 2, box_h / 2), TEXT, font=font, anchor="mm", fill=255)

    # wear: soft blotches plus fine speckle, and the weakest WEAR share of the ink doesn't print
    rng = np.random.default_rng(7)
    blotches = Image.fromarray((rng.random((box_h // 14 + 2, box_w // 14 + 2)) * 255).astype(np.uint8))
    blotches = np.asarray(blotches.resize((box_w, box_h), Image.BICUBIC), np.float32) / 255
    worn = 0.6 * blotches + 0.4 * rng.random((box_h, box_w))
    printed = worn > np.quantile(worn[np.asarray(ink) > 0], WEAR)
    alpha = (np.asarray(ink, np.float32) * printed * OPACITY).astype(np.uint8)

    img = Image.new("RGBA", (box_w, box_h), INK)
    img.putalpha(Image.fromarray(alpha))
    return img.rotate(TILT, resample=Image.BICUBIC, expand=True)

def add_stamp(img, t):
    if t < LAND - DROP:
        return img
    p = min((t - (LAND - DROP)) / DROP, 1)
    scale = START_SCALE + (1 - START_SCALE) * p * p   # speeds up as it comes down, like a hand pressing
    s = full.resize((round(full.width * scale), round(full.height * scale)), Image.LANCZOS)
    if p < 1:
        s.putalpha(s.getchannel("A").point(lambda a: a * p))
    img.alpha_composite(s, (round(w * X - s.width / 2), round(h * Y - s.height / 2)))
    return img

def jolt(img, t):
    """Knock the whole frame sideways and down on impact, settling over JOLT_LEN (a slight zoom hides the edges)."""
    s = t - LAND
    if s < 0 or s >= JOLT_LEN:
        return img
    k = 1 - s / JOLT_LEN
    zoom = 1 + 0.04 * k
    dx, dy = JOLT * k * math.sin(t * 83), JOLT * k * math.cos(t * 61)
    cw, ch = w / zoom, h / zoom
    x0, y0 = (w - cw) / 2 + dx / zoom, (h - ch) / 2 + dy / zoom
    return img.resize((w, h), Image.BICUBIC, box=(x0, y0, x0 + cw, y0 + ch))

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):
    """Read every frame of src, stamp and jolt it, and save to out with the audio."""
    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", "18",
                               "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out], stdin=subprocess.PIPE)
    for i in range(math.ceil(duration * fps)):
        raw = reader.stdout.read(w * h * 3)
        if len(raw) < w * h * 3:
            break
        t = i / fps
        img = add_stamp(Image.frombuffer("RGB", (w, h), raw).convert("RGBA"), t).convert("RGB")
        writer.stdin.write(jolt(img, t).tobytes())
    writer.stdin.close()
    reader.wait()
    if writer.wait():
        sys.exit("ffmpeg failed")

src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
full = stamp()
render(src, out)
```

## More captions and text

- [AI chat reply animation **AI chat reply animation**](https://www.recharm.com/effects/ai-chat-typing)
- [Animated bar chart **Animated bar chart**](https://www.recharm.com/effects/animated-bar-chart)
- [Animated checklist **Animated checklist**](https://www.recharm.com/effects/animated-checklist)
- [Animated line graph **Animated line graph**](https://www.recharm.com/effects/animated-line-graph)
- [Animated pie chart **Animated pie chart**](https://www.recharm.com/effects/animated-pie-chart)

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