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 a cursor click highlight to input.mp4 Each click: the second, then where it lands across and down the frame (0-1): (1.0, 0.50, 0.33), (2.4, 0.50, 0.60), (3.8, 0.68, 0.82) Seconds the cursor takes to move to each click, gliding in from the bottom-right corner for the first: 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 a cursor click highlight 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 |
|---|---|---|
CLICKS | (1.0, 0.50, 0.33), (2.4, 0.50, 0.60), (3.8, 0.68, 0.82) | Each click: the second, then where it lands across and down the frame (0-1). |
GLIDE | 0.5 | Seconds the cursor takes to move to each click, gliding in from the bottom-right corner for the first. |
RING_TIME | 0.6 | Seconds each ripple takes to spread and fade. |
RING_SIZE | 0.16 | Widest ring radius as a share of the frame width. |
COLOR | (255, 190, 0) | Ring and glow color. |
CURSOR_SIZE | 0.045 | Pointer height as a share of the frame height. |
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 cursor with click highlights to input.mp4. Draw a white arrow pointer with a 2-pixel dark (20, 20, 20) outline and a soft drop shadow, 4.5% of the frame height tall, its tip being the point that moves. From 0.5 seconds it glides in from just off the bottom-right corner, then clicks three times: at 1.0 seconds at 50% across and 33% down (the dropper cap), at 2.4 seconds at 50% across and 60% down (the label) and at 3.8 seconds at 68% across and 82% down (the acrylic block). It takes 0.5 seconds to glide to each click with ease-in-out, waits there until the next one and stays after the last. On each click the pointer shrinks to 85% for 0.12 seconds, and a yellow (255, 190, 0) ring 1.2% of the frame width thick spreads out from the click point over 0.6 seconds, its radius easing out from 2.4% to 16% of the frame width, fully opaque for the first half and fading to nothing over the second, with a soft yellow glow and a faint dark edge so it shows on light backgrounds; a second ring at half opacity follows 0.15 seconds behind. The rings are hollow, drawn under the pointer, and nothing is on screen before 0.5 seconds. Use Python with Pillow to draw it (no text, and don't rely on ffmpeg's drawtext filter) and ffmpeg to put it 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 cursor-click-highlight.py and run:
python3 cursor-click-highlight.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Cursor click highlight: a pointer glides between spots on the video and a glowing yellow ring ripples out each time it clicks.
Usage: python3 cursor-click-highlight.py input.mp4 output.mp4
Needs ffmpeg and Pillow (pip install pillow). The clicks come from CLICKS below: copy them from your
screen recorder's click log, or pick the spots you want to point at.
"""
import json
import math
import subprocess
import sys
from PIL import Image, ImageDraw, ImageFilter
CLICKS = [(1.0, 0.50, 0.33), # (second, x, y): each click, 0-1 across and down the frame
(2.4, 0.50, 0.60),
(3.8, 0.68, 0.82)]
GLIDE = 0.5 # seconds the cursor takes to move to each click (it enters from the bottom-right corner)
RING_TIME = 0.6 # seconds each ripple takes to spread and fade
RING_SIZE = 0.16 # widest ring radius as a share of the frame width
RING_WIDTH = 0.012 # ring thickness as a share of the frame width
COLOR = (255, 190, 0) # ring and glow color
CURSOR_SIZE = 0.045 # pointer height as a share of the frame height
SS = 2 # draw rings at twice the size and shrink, for smooth edges
def ease(p):
p = min(max(p, 0), 1)
return p * p * (3 - 2 * p)
def cursor_at(t):
"""Where the pointer tip is at time t, in pixels: it glides into each click and waits there until the next."""
x, y = w * 1.05, h * 1.02 # just off the bottom-right corner
for when, cx, cy in CLICKS:
p = ease((t - (when - GLIDE)) / GLIDE)
x, y = x + (cx * w - x) * p, y + (cy * h - y) * p
return x, y
def pointer(scale):
"""A white arrow pointer with a dark outline and a soft shadow, its tip at (pad, pad)."""
s, pad = h * CURSOR_SIZE * scale, 8
pts = [(pad + x * s, pad + y * s) for x, y in
[(0, 0), (0, 1), (0.27, 0.76), (0.44, 1.12), (0.58, 1.05), (0.42, 0.7), (0.75, 0.7)]]
img = Image.new("RGBA", (round(s * 0.8) + pad * 2, round(s * 1.15) + pad * 2))
shadow = Image.new("RGBA", img.size)
ImageDraw.Draw(shadow).polygon([(x + 2, y + 3) for x, y in pts], fill=(0, 0, 0, 110))
img.alpha_composite(shadow.filter(ImageFilter.GaussianBlur(3)))
ImageDraw.Draw(img).polygon(pts, fill=(255, 255, 255), outline=(20, 20, 20), width=2)
return img
def ripple(img, x, y, p, strength):
"""One ring at progress p (0-1): it spreads fast, holds its color, then fades out, over a soft glow."""
if not 0 <= p < 1:
return
r = w * RING_SIZE * (0.15 + 0.85 * (1 - (1 - p) ** 3)) * SS
alpha = strength * (1 if p < 0.5 else 1 - (p - 0.5) / 0.5)
size = round(w * RING_SIZE * SS * 2.6)
layer = Image.new("RGBA", (size, size))
draw = ImageDraw.Draw(layer)
c, width = size / 2, round(w * RING_WIDTH * SS)
draw.ellipse([c - r, c - r, c + r, c + r], outline=(*COLOR, round(255 * alpha)), width=width * 3)
glow = layer.filter(ImageFilter.GaussianBlur(width * 1.5))
ring = Image.new("RGBA", (size, size))
d2 = ImageDraw.Draw(ring)
d2.ellipse([c - r - 2, c - r - 2, c + r + 2, c + r + 2], outline=(0, 0, 0, round(60 * alpha)), width=width + 4)
d2.ellipse([c - r, c - r, c + r, c + r], outline=(*COLOR, round(255 * alpha)), width=width)
glow.alpha_composite(ring)
glow = glow.resize((size // SS, size // SS), Image.LANCZOS)
img.alpha_composite(glow, (round(x - glow.width / 2), round(y - glow.height / 2)))
def overlay(t):
img = Image.new("RGBA", (w, h))
if t < CLICKS[0][0] - GLIDE: # the cursor is still off screen
return img
press = 1.0
for when, cx, cy in CLICKS:
ripple(img, cx * w, cy * h, (t - when) / RING_TIME, 1.0)
ripple(img, cx * w, cy * h, (t - when - 0.15) / RING_TIME, 0.5) # a fainter echo ring just behind
if abs(t - when - 0.06) < 0.06: # the pointer dips while the button is down
press = 0.85
x, y = cursor_at(t)
img.alpha_composite(pointers[press], (round(x - 8), round(y - 8)))
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(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)
pointers = {1.0: pointer(1.0), 0.85: pointer(0.85)}
render(src, out)





