---
title: "How to add a prize wheel spin with Claude (tested prompt and script)"
description: "A rainbow eight-prize wheel in front of the product whirls into a blur, slows to a stop with red \"30% OFF\" under the arrow, and a yellow \"You won 30% off\" banner pops in above it. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/prize-wheel-spin
---

# How to add a prize wheel spin with Claude

A rainbow eight-prize wheel in front of the product whirls into a blur, slows to a stop with red "30% OFF" under the arrow, and a yellow "You won 30% off" banner pops in above it.

Made by the script on this page, from our own test clip.

## Get this effect

1. ### Switch to Code in your [Claude Desktop App](https://claude.com/download)

2. ### Install the skill (once)

   ```
   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 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.

```
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.mp4
```

```
#!/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")
```

## More hook openers

- [Blur-to-focus reveal **Blur-to-focus reveal**](https://www.recharm.com/effects/blur-to-focus-reveal)
- [Breaking news banner **Breaking news banner**](https://www.recharm.com/effects/breaking-news-banner)
- [Camera shake **Camera shake**](https://www.recharm.com/effects/camera-shake)
- [Color reveal **Color reveal**](https://www.recharm.com/effects/color-reveal)
- [Countdown intro **Countdown intro**](https://www.recharm.com/effects/countdown-intro)

## 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](https://www.recharm.com/get-started)
