Get this effect
Switch to Code in your Claude Desktop App
Install the skill (once)
Show the prompt
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/skillsAsk for the effect
Add falling letters to input.mp4 Rows: [("FORMULA", 0.5), ("NEW", 2.3)]
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 falling letters 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 |
|---|---|---|
ROWS | [("FORMULA", 0.5), ("NEW", 2.3)] | Each word and the second its first letter drops, bottom row first; later rows land on top. |
STAGGER | 0.1 | Seconds between one letter dropping and the next, in a shuffled order. |
FLOOR | 0.88 | Where the bottom row comes to rest (0 = top, 1 = bottom). |
WIDTH | 0.88 | The widest row's width as a share of the frame width. |
GRAVITY | 3.2 | How fast letters speed up, in frame heights per second squared. |
BOUNCE | 0.4 | Share of its speed a letter keeps after hitting the floor. |
TILT | 25 | Most a letter is tilted while it falls, in degrees. |
COLOR | (255, 214, 0) | Letter color. |
OUTLINE | (35, 15, 55) | Outline color. |
SEED | 7 | Changes the drop order and tilts. |
Without the skill
Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.
Show the prompt
Add falling letters to input.mp4: the word "FORMULA" in Arial Black (or any heavy bold font) in yellow (255, 214, 0) with a deep ink (35, 15, 55) outline 1.2% of the frame width thick, sized so the word is 88% of the frame width wide, centered across with its baseline resting 88% of the way down. "NEW" in the same font and size rests directly on top of it, centered, its baseline one capital-letter height plus two outline widths higher. Each letter falls on its own: its baseline starts 2% of the frame height above the top edge with no speed, it falls under gravity of 3.2 frame heights per second squared, and when it reaches its resting place it bounces back up keeping 40% of its speed, again and again, until a bounce would be slower than 0.15 frame heights per second, then it stays put. While falling each letter is tilted by a random angle up to 25 degrees either way; each bounce flips the tilt and shrinks it to 40%, and it is upright once at rest. The letters of FORMULA start falling one every 0.1 seconds from 0.5 seconds in the order R, O, M, F, U, A, L; the letters of NEW start one every 0.1 seconds from 2.3 seconds in the order N, W, E. Nothing shows before 0.5 seconds and everything stays to the end. Use Python with Pillow to draw the letters (don't rely on ffmpeg's drawtext filter) and ffmpeg to put them on the video, keep the audio, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as falling-letters.py and run:
python3 falling-letters.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Falling letters: letters drop in from the top one by one, bounce off the floor and pile up into a headline.
Usage: python3 falling-letters.py input.mp4 output.mp4
Needs ffmpeg and Pillow (pip install pillow).
"""
import json
import math
import random
import subprocess
import sys
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
ROWS = [("FORMULA", 0.5), # (word, second its first letter drops), bottom row first: later rows land on top
("NEW", 2.3)]
STAGGER = 0.1 # seconds between one letter dropping and the next, in a shuffled order
FLOOR = 0.88 # where the bottom row comes to rest (0 = top, 1 = bottom)
WIDTH = 0.88 # the widest row's width as a share of the frame width
GRAVITY = 3.2 # frame heights per second squared
BOUNCE = 0.4 # share of its speed a letter keeps after hitting the floor
TILT = 25 # most a letter is tilted while it falls, in degrees; each bounce straightens it
COLOR = (255, 214, 0) # letter color
OUTLINE = (35, 15, 55) # outline color, a deep ink
SEED = 7 # changes the drop order and tilts
FONTS = ["/System/Library/Fonts/Supplemental/Arial Black.ttf", # macOS
"/System/Library/Fonts/Supplemental/Arial Bold.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 letters():
"""Each letter as (tile, center x, resting baseline y, drop time, starting tilt)."""
widest = max((word for word, _ in ROWS), key=len)
font = ImageFont.truetype(FONT_FILE, 100)
font = ImageFont.truetype(FONT_FILE, round(100 * w * WIDTH / font.getlength(widest)))
stroke = max(2, round(w * 0.012))
cap = -font.getbbox("".join(word for word, _ in ROWS), anchor="ls")[1]
pad = stroke + round(cap * 0.15)
rng = random.Random(SEED)
out = []
for row, (word, start) in enumerate(ROWS):
baseline = h * FLOOR - row * (cap + 2 * stroke)
left = (w - font.getlength(word)) / 2
order = rng.sample(range(len(word)), len(word))
for i, ch in enumerate(word):
size = (round(font.getlength(ch)) + 2 * pad, cap + 2 * pad)
tile = Image.new("RGBA", size)
ImageDraw.Draw(tile).text((pad, pad + cap), ch, font=font, anchor="ls", fill=COLOR,
stroke_width=stroke, stroke_fill=OUTLINE)
x = left + font.getlength(word[:i]) + font.getlength(ch) / 2
out.append((tile, x, baseline, start + order.index(i) * STAGGER, rng.uniform(-TILT, TILT), cap))
return out
def fall(drop, baseline, tilt):
"""Baseline y and tilt of a letter on every frame: it starts just above the frame and falls under gravity,
bouncing off its resting place with a little less speed each time until it stops."""
y, v, steps = -0.02 * h, 0.0, 20
g, floor, path = GRAVITY * h, baseline, []
for i in range(frames):
t = i / fps
if t < drop:
path.append(None)
continue
for _ in range(steps):
if v == 0 and y == floor:
break
v += g / fps / steps
y += v / fps / steps
if y >= floor:
y, v, tilt = floor, -v * BOUNCE, -tilt * BOUNCE
if abs(v) < 0.15 * h:
v, tilt = 0.0, 0.0
path.append((y, tilt))
return path
def overlay(i):
img = Image.new("RGBA", (w, h))
for (tile, x, _, _, _, cap), path in zip(tiles, paths):
if path[i] is None:
continue
y, tilt = path[i]
pic = tile.rotate(tilt, resample=Image.BICUBIC, expand=True) if tilt else tile
img.alpha_composite(pic, (round(x - pic.width / 2), round(y - cap / 2 - pic.height / 2)))
return img
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(frame) 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(frames):
ff.stdin.write(overlay(i).tobytes())
ff.stdin.close()
if ff.wait():
sys.exit("ffmpeg failed")
src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
frames = math.ceil(duration * fps)
tiles = letters()
paths = [fall(drop, baseline, tilt) for _, _, baseline, drop, tilt, _ in tiles]
render(src, out)





