---
title: "How to add an emoji rain with Claude (tested prompt and script)"
description: "Dozens of heart-eyes emojis drop in from the top edge half a second in and keep pouring down over the product to the end, the bigger ones falling faster and each rocking from side to side. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/emoji-rain
---

# How to add an emoji rain with Claude

Dozens of heart-eyes emojis drop in from the top edge half a second in and keep pouring down over the product to the end, the bigger ones falling faster and each rocking from side to side.

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 an emoji rain to input.mp4
   The emoji that rains down (heart eyes): "\U0001F60D"
   Second the first emojis drop in at the top edge: 0.5
   ```

## 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 an emoji 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                                                 |
| ------- | -------------- | ------------------------------------------------------------ |
| `EMOJI` | `"\U0001F60D"` | The emoji that rains down (heart eyes).                      |
| `START` | `0.5`          | Second the first emojis drop in at the top edge.             |
| `COUNT` | `36`           | Emojis in the rain.                                          |
| `SIZE`  | `(0.10, 0.19)` | Emoji width range, as a share of the frame width.            |
| `SPEED` | `(0.32, 0.50)` | Fall speed range, in frame heights per second.               |
| `ROCK`  | `18`           | How far each emoji tilts either way as it falls, in degrees. |
| `SEED`  | `4`            | Change for a different rain.                                 |

## Without the skill

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

```
Add an emoji rain to input.mp4 (540x960). Render the heart-eyes emoji (U+1F60D) once with Pillow from a color emoji font (Apple Color Emoji at size 160 on macOS, with embedded_color=True), crop it to its edges and reuse it for every drop. Rain 36 copies down the frame starting at 0.5 seconds: each is scaled to 10% to 19% of the frame width (54 to 103 pixels; bigger ones fall faster, 0.32 to 0.50 frame heights per second, and are drawn in front), spread evenly from 6% to 94% across, and queued evenly from 8% to 138% of the frame height above the top edge so they enter one after another. Rock each emoji up to 18 degrees either way over a 1.2 to 2.2 second cycle as it falls. An emoji that falls 15% past the bottom comes back in at the top, so the rain keeps going to the end. Nothing shows before 0.5 seconds, the lower half stays clear until after 1 second, and by 3 seconds emojis cover the whole frame while the product stays visible between them. Use Python with numpy and Pillow to draw it and ffmpeg to put it on the video (don't use ffmpeg's drawtext filter; draw any text or emoji with Pillow), keep the audio, and save it as output.mp4.
```

## Run it without Claude

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

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

```
#!/usr/bin/env python3
"""Emoji rain: dozens of heart-eyes emojis pour down from the top of the frame, rocking as they fall.

Usage: python3 emoji-rain.py input.mp4 output.mp4
Needs ffmpeg, numpy and Pillow (pip install numpy pillow), and a color emoji font (built into macOS and Windows).
"""
import json
import math
import subprocess
import sys
from pathlib import Path

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

EMOJI = "\U0001F60D"            # the emoji that rains down (heart eyes)
START = 0.5                     # second the first emojis drop in at the top edge
COUNT = 36                      # emojis in the rain
SIZE = (0.10, 0.19)             # emoji width range, as a share of the frame width
SPEED = (0.32, 0.50)            # fall speed range, in frame heights per second (bigger emojis fall faster)
ROCK = 18                       # how far each emoji tilts either way as it falls, in degrees
SEED = 4                        # change for a different rain
FONTS = [("/System/Library/Fonts/Apple Color Emoji.ttc", 160),        # macOS (color emoji fonts come in fixed sizes)
         ("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", 109),  # Linux
         ("C:/Windows/Fonts/seguiemj.ttf", 160)]                      # Windows
FONT_FILE, FONT_SIZE = next((f for f in FONTS if Path(f[0]).exists()), None) or sys.exit("No emoji font: add one to FONTS")

def emoji():
    """The emoji drawn once at full size, cropped to its edges."""
    img = Image.new("RGBA", (FONT_SIZE * 2, FONT_SIZE * 2))
    ImageDraw.Draw(img).text((FONT_SIZE // 2, FONT_SIZE // 2), EMOJI, embedded_color=True,
                             font=ImageFont.truetype(FONT_FILE, FONT_SIZE))
    return img.crop(img.getbbox())

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"])

src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
face = emoji()
rng = np.random.default_rng(SEED)
drops = []
for k in range(COUNT):                                   # emojis queue up evenly above the frame
    depth = rng.uniform(0, 1)                            # 0 = far (small, slow), 1 = near (big, fast)
    size = round((SIZE[0] + depth * (SIZE[1] - SIZE[0])) * w)
    across = (k * 0.618 + rng.uniform(0, 0.1)) % 1      # golden-ratio steps spread them evenly across
    drops.append({"sprite": face.resize((size, size), Image.LANCZOS),
                  "x": (0.06 + 0.88 * across) * w, "y": -(0.08 + 1.3 * k / COUNT) * h,
                  "speed": (SPEED[0] + depth * (SPEED[1] - SPEED[0])) * h,
                  "rock": rng.uniform(1.2, 2.2), "phase": rng.uniform(0, 2 * math.pi), "depth": depth})
drops.sort(key=lambda d: d["depth"])                     # draw far emojis first so near ones cover them

def draw_rain(img, t):
    """Paste every emoji at its place at time t onto the frame."""
    age = t - START
    span = h * 1.3                                       # an emoji that falls off the bottom comes back in at the top
    for d in drops:
        y = d["y"] + d["speed"] * age
        if y > h * 1.15:
            y = (y + 0.15 * h) % span - 0.15 * h
        angle = ROCK * math.sin(2 * math.pi * age / d["rock"] + d["phase"])
        sprite = d["sprite"].rotate(angle, expand=True, resample=Image.BICUBIC)
        img.paste(sprite, (round(d["x"] - sprite.width / 2), round(y - sprite.height / 2)), sprite)

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)
i = 0
while raw := reader.stdout.read(w * h * 3):
    t = i / fps
    if t >= START:
        img = Image.frombytes("RGB", (w, h), raw)
        draw_rain(img, t)
        raw = img.tobytes()
    writer.stdin.write(raw)
    i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
    sys.exit("ffmpeg failed")
```

## More ugc and social proof

- [Comment reply overlay **Comment reply overlay**](https://www.recharm.com/effects/comment-reply-overlay)
- [Day counter **Day counter**](https://www.recharm.com/effects/day-counter)
- [Floating hearts **Floating hearts**](https://www.recharm.com/effects/floating-hearts)
- [Follow button animation **Follow button animation**](https://www.recharm.com/effects/follow-button-animation)
- [Live stream overlay **Live stream overlay**](https://www.recharm.com/effects/live-stream-overlay)

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