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 shoppable price tags to input.mp4 Tags: Glow Serum $34 at 0.8s, Acrylic Riser $12 at 1.6s
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 shoppable price tags 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 |
|---|---|---|
TAGS | Glow Serum $34 at 0.8s, Acrylic Riser $12 at 1.6s | Each tag's name, price, start second, dot position and card position. |
CARD | (24, 20, 34, 235) | Card, rim and line color. |
PRICE_COLOR | (255, 214, 0) | Price text color. |
PULSE | 1.2 | Seconds between pulses of the ring round each dot. |
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 two shoppable price tags to input.mp4. Each tag is a white dot 1.6% of the frame width in radius with a dark (24, 20, 34) rim, a 0.6%-wide dark (24, 20, 34) line from the dot to the center of a card, and a rounded card filled with (24, 20, 34) at 92% opacity (corner radius 2.4% of the width, padding 2.8%) showing the item name in white bold at 4% of the width over the price in yellow (255, 214, 0) bold at 6% of the width. Tag 1: "Glow Serum" / "$34", dot at 52% across and 58% down (on the bottle's label), card centered at 22% across and 40% down, starting at 0.8 seconds. Tag 2: "Acrylic Riser" / "$12", dot at 78% across and 85% down (on the acrylic block), card centered at 76% across and 69% down, starting at 1.6 seconds. For each tag the dot pops in over 0.3 seconds with a slight overshoot, the line then draws out to the card over 0.2 seconds, and the card then fades in while growing from 80% to full size over 0.25 seconds; once in, a thin white ring pulses out from the dot every 1.2 seconds, growing and fading. Both tags stay to the end. Use Python with Pillow to draw the tags and their text and ffmpeg to put them on the video (don't rely on ffmpeg's drawtext filter), keep the audio, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as shoppable-price-tags.py and run:
python3 shoppable-price-tags.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Shoppable price tags: pulsing white dots pop onto items in the shot, each linked to a dark card with its name and price.
Usage: python3 shoppable-price-tags.py input.mp4 output.mp4
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
# name, price, second it appears, dot center, card center (0-1 across and down the frame)
TAGS = [("Glow Serum", "$34", 0.8, (0.52, 0.58), (0.22, 0.40)),
("Acrylic Riser", "$12", 1.6, (0.78, 0.85), (0.76, 0.69))]
CARD = (24, 20, 34, 235) # card fill (RGBA)
PRICE_COLOR = (255, 214, 0) # price text color
POP = 0.3 # seconds the dot takes to pop in
LINK = 0.2 # seconds the line takes to reach the card, once the dot is in
FADE = 0.25 # seconds the card takes to grow in, once the line is there
PULSE = 1.2 # seconds between pulses of the ring round each dot
SS = 4 # supersampling, for smooth circles and corners
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 card(name, price):
"""The name over the price on a rounded dark card, drawn large and shrunk so the edges are smooth."""
name_font = ImageFont.truetype(FONT_FILE, round(w * 0.040) * SS)
price_font = ImageFont.truetype(FONT_FILE, round(w * 0.060) * SS)
pad, gap = round(w * 0.028) * SS, round(w * 0.010) * SS
nw, nh = name_font.getbbox(name, anchor="lt")[2:]
pw, ph = price_font.getbbox(price, anchor="lt")[2:]
cw, ch = max(nw, pw) + 2 * pad, nh + gap + ph + 2 * pad
img = Image.new("RGBA", (cw, ch))
draw = ImageDraw.Draw(img)
draw.rounded_rectangle([0, 0, cw - 1, ch - 1], radius=round(w * 0.024) * SS, fill=CARD)
draw.text((pad, pad), name, font=name_font, anchor="lt", fill="white")
draw.text((pad, pad + nh + gap), price, font=price_font, anchor="lt", fill=PRICE_COLOR)
return img.resize((cw // SS, ch // SS), Image.LANCZOS)
def dot(scale, pulse):
"""A white dot with a dark rim, and a ring that grows and fades as pulse goes 0 -> 1."""
r = w * 0.016 * scale
size = round(w * 0.12)
img = Image.new("RGBA", (size * SS, size * SS))
draw = ImageDraw.Draw(img)
c = size * SS / 2
def circle(radius, **style):
draw.ellipse([c - radius * SS, c - radius * SS, c + radius * SS, c + radius * SS], **style)
if scale >= 1:
circle(r * (1.3 + 2.2 * pulse), outline=(255, 255, 255, round(220 * (1 - pulse))), width=round(w * 0.005) * SS)
circle(r * 1.35, fill=CARD)
circle(r, fill="white")
return img.resize((size, size), Image.LANCZOS)
def overlay(t):
img = Image.new("RGBA", (w, h))
draw = ImageDraw.Draw(img)
for (name, price, start, (dx, dy), (cx, cy)), sprite in zip(TAGS, cards):
if t < start:
continue
dot_xy, card_xy = (w * dx, h * dy), (w * cx, h * cy)
link = clamp((t - start - POP) / LINK)
if link > 0:
end = (dot_xy[0] + (card_xy[0] - dot_xy[0]) * link, dot_xy[1] + (card_xy[1] - dot_xy[1]) * link)
draw.line([dot_xy, end], fill=CARD, width=round(w * 0.006))
grow = clamp((t - start - POP - LINK) / FADE)
if grow > 0:
s = 0.8 + 0.2 * grow
c = sprite.resize((round(sprite.width * s), round(sprite.height * s)), Image.LANCZOS)
c.putalpha(c.getchannel("A").point(lambda a: round(a * grow)))
img.alpha_composite(c, (round(card_xy[0] - c.width / 2), round(card_xy[1] - c.height / 2)))
pop = ease_out_back(clamp((t - start) / POP))
if pop > 0.05:
d = dot(pop, ((t - start - POP) / PULSE) % 1 if t >= start + POP else 0)
img.alpha_composite(d, (round(dot_xy[0] - d.width / 2), round(dot_xy[1] - d.height / 2)))
return img
def clamp(p):
return min(max(p, 0), 1)
def ease_out_back(p):
"""0 -> 1 with a small overshoot past 1 before settling, which reads as a 'pop'."""
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 = [card(name, price) for name, price, *_ in TAGS]
render(src, out)





