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 prize wheel spin to input.mp4 Prizes: 30% OFF, FREE GIFT, 10% OFF, FREE SHIP, 20% OFF, 2 FOR 1, 5% OFF, 50% OFF Colors: red, white, purple, yellow, teal, pink, navy, orange Banner: YOU WON, 30% OFF
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 prize wheel spin 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 |
|---|---|---|
PRIZES | 30% OFF, FREE GIFT, 10% OFF, FREE SHIP, 20% OFF, 2 FOR 1, 5% OFF, 50% OFF | Slice labels clockwise, starting with the one the wheel lands on. |
COLORS | red, white, purple, yellow, teal, pink, navy, orange | One RGB color per slice. |
SPIN_START, SPIN_END | 0.3, 3.8 | Seconds the wheel spins. |
TURNS | 5 | Full turns before it lands. |
SIZE | 0.86 | Wheel width as a share of the frame width. |
X, Y | 0.5, 0.65 | Wheel center, 0-1 across and down the frame. |
BANNER_AT | 4.0 | Second the banner pops in. |
BANNER | YOU WON, 30% OFF | The banner's two lines. |
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 prize wheel spin to input.mp4. Draw a wheel 86% of the frame width across, centered at 50% across and 65% down, with a soft dark drop shadow: a dark (30, 20, 50) rim 6% of the wheel's width thick with 24 small cream (255, 245, 200) dots round it, eight equal slices with thin white dividers and a white hub (18% of the wheel's width) with a dark outline. Clockwise from the top the slices are red (230, 30, 45) "30% / OFF", white "FREE / GIFT", purple (120, 60, 220) "10% / OFF", yellow (255, 214, 0) "FREE / SHIP", teal (0, 170, 160) "20% / OFF", pink (255, 60, 150) "2 FOR / 1", navy (30, 40, 90) "5% / OFF" and orange (255, 140, 0) "50% / OFF", each label in bold on two lines at 66% of the radius, upright when its slice is at the top, dark text on light slices and white on dark ones. Put a white downward triangle with a dark outline, 11% of the frame width, at 12 o'clock over the rim as the pointer; it doesn't move. The wheel starts turned 160 degrees counterclockwise from its landing position, holds until 0.3 seconds, then spins clockwise 5 full turns plus those 160 degrees, ending at 3.8 seconds with the red 30% OFF slice under the pointer; ease it out with a cubic curve so it starts fast and slows the whole way, and motion-blur each frame by averaging the wheel at several angles across the frame's turn. At 4.0 seconds pop in a yellow (255, 214, 0) rounded banner 84% of the frame width wide and 14% of the frame height tall, centered at 13% down, with a dark outline and "YOU WON" over "30% OFF" in dark bold text; it scales up over 0.35 seconds with a slight overshoot and stays. Use Python with Pillow to draw the wheel, labels and banner 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 prize-wheel-spin.py and run:
python3 prize-wheel-spin.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Prize wheel spin: an eight-prize wheel whirls, slows to a stop on "30% OFF" and a "You won" banner pops in.
Usage: python3 prize-wheel-spin.py input.mp4 output.mp4
Needs ffmpeg, Pillow and numpy (pip install pillow numpy).
"""
import json
import math
import subprocess
import sys
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw, ImageFilter, ImageFont
PRIZES = [("30%", "OFF"), ("FREE", "GIFT"), ("10%", "OFF"), ("FREE", "SHIP"), # clockwise from the winner
("20%", "OFF"), ("2 FOR", "1"), ("5%", "OFF"), ("50%", "OFF")]
COLORS = [(230, 30, 45), (255, 255, 255), (120, 60, 220), (255, 214, 0), # one per prize
(0, 170, 160), (255, 60, 150), (30, 40, 90), (255, 140, 0)]
SPIN_START, SPIN_END = 0.3, 3.8 # seconds the wheel spins; it slows down the whole way
TURNS = 5 # full turns before it lands
SIZE = 0.86 # wheel width as a share of the frame width
X, Y = 0.5, 0.65 # wheel center (0-1 across and down the frame)
BANNER_AT = 4.0 # second the "You won" banner pops in
BANNER = ("YOU WON", "30% OFF") # banner's two lines
BANNER_COLOR = (255, 214, 0)
INK = (30, 20, 50) # dark text and rim color
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 font(size):
return ImageFont.truetype(FONT_FILE, round(size))
def wheel():
"""The wheel with the winning prize at 12 o'clock, every label upright when its slice is at the top."""
d = round(w * SIZE)
r, step = d / 2, 360 / len(PRIZES)
img = Image.new("RGBA", (d, d))
draw = ImageDraw.Draw(img)
draw.ellipse([0, 0, d - 1, d - 1], fill=INK)
inner = [d * 0.06, d * 0.06, d * 0.94, d * 0.94]
for i, color in enumerate(COLORS):
mid = -90 + i * step # Pillow angles run clockwise from 3 o'clock
draw.pieslice(inner, mid - step / 2, mid + step / 2, fill=color, outline="white", width=3)
for i in range(24): # light bulbs round the rim
a, b = math.radians(i * 15 + 7.5), d * 0.012
bx, by = r + r * 0.94 * math.sin(a), r - r * 0.94 * math.cos(a)
draw.ellipse([bx - b, by - b, bx + b, by + b], fill=(255, 245, 200))
for i, ((top, bottom), color) in enumerate(zip(PRIZES, COLORS)):
ink = INK if sum(color) > 450 else "white"
label = Image.new("RGBA", (round(d * 0.3), round(d * 0.2)))
ld = ImageDraw.Draw(label)
ld.text((label.width / 2, label.height * 0.32), top, font=font(d * 0.072), anchor="mm", fill=ink)
ld.text((label.width / 2, label.height * 0.75), bottom, font=font(d * 0.054), anchor="mm", fill=ink)
label = label.rotate(-i * step, resample=Image.BICUBIC, expand=True)
a = math.radians(i * step)
cx, cy = r + r * 0.66 * math.sin(a), r - r * 0.66 * math.cos(a)
img.alpha_composite(label, (round(cx - label.width / 2), round(cy - label.height / 2)))
draw.ellipse([r - d * 0.09, r - d * 0.09, r + d * 0.09, r + d * 0.09], fill="white", outline=INK, width=round(d * 0.02))
return img
def angle(t):
"""Clockwise turn in degrees: starts fast, eases out (cubic) and lands with the winner back at the top."""
total = TURNS * 360 + 160
p = min(max((t - SPIN_START) / (SPIN_END - SPIN_START), 0), 1)
return total * (1 - (1 - p) ** 3) - total
def spun(t):
"""The wheel at time t, motion-blurred by averaging enough in-between angles to cover one frame's turn."""
a0, a1 = angle(t), angle(t + 1 / fps)
n = max(1, math.ceil(abs(a1 - a0) / 3))
layers = [np.asarray(disc.rotate(-(a0 + (a1 - a0) * k / n), resample=Image.BICUBIC), np.float32) for k in range(n)]
return Image.fromarray(np.mean(layers, axis=0).astype(np.uint8))
def pointer():
"""A white arrow at 12 o'clock pointing down into the wheel."""
s = round(w * 0.11)
img = Image.new("RGBA", (s, s))
ImageDraw.Draw(img).polygon([(s * 0.1, s * 0.05), (s * 0.9, s * 0.05), (s / 2, s * 0.95)],
fill="white", outline=INK, width=max(2, s // 14))
return img
def banner():
bw, bh = round(w * 0.84), round(h * 0.14)
img = Image.new("RGBA", (bw, bh))
draw = ImageDraw.Draw(img)
draw.rounded_rectangle([0, 0, bw - 1, bh - 1], radius=bh * 0.25, fill=BANNER_COLOR, outline=INK, width=4)
draw.text((bw / 2, bh * 0.3), BANNER[0], font=font(bh * 0.26), anchor="mm", fill=INK)
draw.text((bw / 2, bh * 0.66), BANNER[1], font=font(bh * 0.42), anchor="mm", fill=INK)
return img
def shadow():
img = Image.new("RGBA", (w, h))
d = disc.width
ImageDraw.Draw(img).ellipse([w * X - d / 2, h * Y - d / 2 + d * 0.04, w * X + d / 2, h * Y + d / 2 + d * 0.04], fill=(0, 0, 0, 120))
return img.filter(ImageFilter.GaussianBlur(d * 0.03))
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 overlay(t):
img = base.copy()
img.alpha_composite(spun(t), (round(w * X - disc.width / 2), round(h * Y - disc.height / 2)))
img.alpha_composite(arrow, (round(w * X - arrow.width / 2), round(h * Y - disc.height / 2 - arrow.height * 0.45)))
scale = ease_out_back((t - BANNER_AT) / 0.35) if t >= BANNER_AT else 0
if scale > 0.02:
b = won.resize((round(won.width * scale), max(1, round(won.height * scale))), Image.LANCZOS)
img.alpha_composite(b, (round(w / 2 - b.width / 2), round(h * 0.13 - b.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:format=duration", "-of", "json", path]))
return info["streams"][0]["width"], info["streams"][0]["height"], float(info["format"]["duration"])
src, out = sys.argv[1:3]
w, h, duration = probe(src)
fps = 30
disc, arrow, won = wheel(), pointer(), banner()
base = shadow()
# overlay frames come in on stdin and go over the video, which keeps its own audio
ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", src,
"-f", "rawvideo", "-pix_fmt", "rgba", "-s", f"{w}x{h}", "-r", str(fps), "-i", "-",
"-filter_complex", f"[0:v]fps={fps}[v0];[v0][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")





