How to add a keystroke overlay with Claude

White key caps reading "Cmd + C", then "Cmd + V", then "Shift + Cmd + Z" pop up on a dark panel near the bottom, each one as the shortcut is pressed.

Get this effect

  1. Switch to Code in your Claude Desktop App

  2. 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/skills
  3. Ask for the effect

    Add a keystroke overlay to input.mp4
    Presses: Cmd + C at 0.8-1.9s, Cmd + V at 2.2-3.3s, Shift + Cmd + Z at 3.6-4.9s

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 keystroke overlay 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.

SettingDefaultWhat it does
PRESSESCmd + C at 0.8-1.9s, Cmd + V at 2.2-3.3s, Shift + Cmd + Z at 3.6-4.9sEach shortcut's start and end second and the keys held together.
Y0.82Where the row of keys is centered (0 = top, 1 = bottom).
KEY_H0.13Key cap height as a share of the frame width.
POP0.15Seconds a shortcut takes to pop in and to fade out.
CAP, EDGE, INK(255, 255, 255), (170, 170, 178), (25, 25, 30)Key cap face, raised edge and label colors.

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 a keystroke overlay to input.mp4 that shows three keyboard shortcuts as key caps: "Cmd" + "C" from 0.8 to 1.9 seconds, "Cmd" + "V" from 2.2 to 3.3 seconds and "Shift" + "Cmd" + "Z" from 3.6 to 4.9 seconds. Draw each key as a pure white (255, 255, 255) rounded key cap 13% of the frame width tall (at least as wide as it is tall) with its name in dark (25, 25, 30) bold text at 42% of the key height, a gray (170, 170, 178) edge peeking out 8% of the key height below it so it looks raised, and a white "+" between keys. Put each shortcut's keys on a dark rounded panel (15, 15, 20) at 75% opacity, centered across the frame and 82% of the way down. Each shortcut pops in over 0.15 seconds, growing from 85% to full size with a slight overshoot, and fades out over its last 0.15 seconds; nothing shows between shortcuts. Use Python with Pillow to draw the keys (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 keystroke-overlay.py and run:

python3 keystroke-overlay.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Keystroke overlay: white key caps pop up near the bottom showing each shortcut as it is pressed.

Usage: python3 keystroke-overlay.py input.mp4 output.mp4
The shortcuts and their times go in PRESSES below, from your recorder's key log or written by hand.
Needs ffmpeg and Pillow (pip install pillow).
"""
import json
import math
import subprocess
import sys
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont

PRESSES = [(0.8, 1.9, ["Cmd", "C"]),            # (start second, end second, keys held together)
           (2.2, 3.3, ["Cmd", "V"]),
           (3.6, 4.9, ["Shift", "Cmd", "Z"])]
Y = 0.82                 # where the row of keys is centered (0 = top, 1 = bottom)
KEY_H = 0.13             # key cap height as a share of the frame width
POP = 0.15               # seconds a combo takes to pop in (and to fade out)
CAP = (255, 255, 255)    # key cap face
EDGE = (170, 170, 178)   # key cap's lower edge, which makes it look raised
INK = (25, 25, 30)       # key label color
PANEL = (15, 15, 20, 190)  # dark rounded panel behind the keys
FONTS = ["/System/Library/Fonts/Supplemental/Arial Bold.ttf",     # macOS
         "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",  # Linux
         "C:/Windows/Fonts/arialbd.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 combo(keys):
    """One shortcut drawn as raised key caps joined by plus signs on a dark panel."""
    kh = round(w * KEY_H)
    font = ImageFont.truetype(FONT_FILE, round(kh * 0.42))
    edge, gap, pad = round(kh * 0.08), round(kh * 0.45), round(kh * 0.28)
    widths = [max(kh, round(font.getlength(k) + kh * 0.6)) for k in keys]
    width = sum(widths) + gap * (len(keys) - 1) + 2 * pad
    img = Image.new("RGBA", (width, kh + edge + 2 * pad))
    draw = ImageDraw.Draw(img)
    draw.rounded_rectangle([0, 0, img.width - 1, img.height - 1], radius=pad, fill=PANEL)
    x = pad
    for i, (key, kw) in enumerate(zip(keys, widths)):
        r = round(kh * 0.18)
        draw.rounded_rectangle([x, pad + edge, x + kw, pad + kh + edge], radius=r, fill=EDGE)
        draw.rounded_rectangle([x, pad, x + kw, pad + kh], radius=r, fill=CAP)
        draw.text((x + kw / 2, pad + kh / 2), key, font=font, anchor="mm", fill=INK)
        x += kw
        if i < len(keys) - 1:
            draw.text((x + gap / 2, pad + kh / 2), "+", font=font, anchor="mm", fill="white")
            x += gap
    return img


def overlay(t):
    img = Image.new("RGBA", (w, h))
    for (start, end, _), card in zip(PRESSES, cards):
        if start <= t < end:
            scale = 0.85 + 0.15 * ease_out_back((t - start) / POP)
            fade = min(1, (end - t) / POP)
            c = card.resize((round(card.width * scale), round(card.height * scale)), Image.LANCZOS)
            if fade < 1:
                c.putalpha(c.getchannel("A").point(lambda a: round(a * fade)))
            img.alpha_composite(c, (round(w / 2 - c.width / 2), round(h * Y - c.height / 2)))
    return img


def ease_out_back(p):
    """0 -> 1 with a small overshoot past 1 before settling, which reads as a 'pop'."""
    p = min(max(p, 0), 1)
    return 1 + 2.70158 * (p - 1) ** 3 + 1.70158 * (p - 1) ** 2


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)
cards = [combo(keys) for _, _, keys in PRESSES]
render(src, out)

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