---
title: "How to add a spinning vinyl record with Claude (tested prompt and script)"
description: "A glossy black record with the product shot as its label turns clockwise in the bottom-right corner at 33 rpm, its yellow \"Side A\" band circling once every 1.8 seconds. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/spinning-vinyl
---

# How to add a spinning vinyl record with Claude

A glossy black record with the product shot as its label turns clockwise in the bottom-right corner at 33 rpm, its yellow "Side A" band circling once every 1.8 seconds.

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 spinning vinyl record to input.mp4
   Band color, band text: (255, 214, 0), SIDE A
   ```

## 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 spinning vinyl record 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                                   |
| ----------------------- | ------------------------- | ---------------------------------------------- |
| `RPM`                   | `100 / 3`                 | Turns per minute, clockwise (33 1/3).          |
| `SIZE`                  | `0.56`                    | Record width as a share of the frame width.    |
| `X, Y`                  | `0.70, 0.83`              | Record center, 0-1 across and down the frame.  |
| `LABEL`                 | `0.42`                    | Center label width as a share of the record's. |
| `ART_AT`                | `0.0`                     | Second of the video used as the album art.     |
| `ART_BOX`               | `(0.0, 0.2, 1.0, 0.7625)` | Square part of that frame shown on the label.  |
| `BAND_COLOR, BAND_TEXT` | `(255, 214, 0), SIDE A`   | The label's printed band and its words.        |
| `VINYL`                 | `(18, 18, 22)`            | Record color.                                  |

## Without the skill

Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.

```
Add a spinning vinyl record to input.mp4. Draw a black (18, 18, 22) record 56% of the frame width across, centered at 70% across and 83% down, with fine concentric grooves, a soft dark drop shadow, a thin bright rim and two soft white highlight wedges on the vinyl (top-right and bottom-left) that stay still while the record turns. Its center label is 42% of the record's width: a square crop of the video's first frame (the full width, from 20% to 76.25% down) masked to a circle, with a yellow (255, 214, 0) band across it from 70% to 90% of the label's height reading "SIDE A" in dark bold text, and a small dark spindle hole 5% of the record's width at the center. Spin the record clockwise at a steady 33 1/3 rpm (200 degrees a second, one turn every 1.8 seconds) for the whole video, starting with the band at the bottom. Use Python with Pillow and numpy to draw it and ffmpeg to put it 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 `spinning-vinyl.py` and run:

```
python3 spinning-vinyl.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Spinning vinyl: a black record with the product as its album art spins at 33 rpm in the bottom-right corner.

Usage: python3 spinning-vinyl.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

RPM = 100 / 3              # turns per minute (33 1/3, a long-player's speed), clockwise
SIZE = 0.56                # record width as a share of the frame width
X, Y = 0.70, 0.83          # record center (0-1 across and down the frame)
LABEL = 0.42               # center label width as a share of the record's
ART_AT = 0.0               # second of the video used as the album art
ART_BOX = (0.0, 0.2, 1.0, 0.7625)  # square part of that frame on the label (x0, y0, x1, y1 as 0-1 of the frame)
BAND_COLOR = (255, 214, 0)  # the label's printed band
BAND_TEXT = "SIDE A"
VINYL = (18, 18, 22)       # record 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 disc_mask(d):
    mask = Image.new("L", (d * 4, d * 4))
    ImageDraw.Draw(mask).ellipse([0, 0, d * 4 - 1, d * 4 - 1], fill=255)
    return mask.resize((d, d), Image.LANCZOS)

def record(art):
    """The record as it looks unturned: grooved vinyl, the album art label with its band, the spindle hole."""
    yy, xx = np.mgrid[:d, :d] - (d - 1) / 2
    r = np.hypot(xx, yy) / (d / 2)
    grooves = 6 * np.sin(r * 260) + np.where((r > 0.62) & (r < 0.64) | (r > 0.86) & (r < 0.875), -8, 0)
    vinyl = np.clip(np.array(VINYL, np.float32) + grooves[..., None], 0, 255).astype(np.uint8)
    img = Image.fromarray(vinyl).convert("RGBA")
    img.putalpha(disc_mask(d))

    ld = round(d * LABEL)
    label = art.resize((ld, ld), Image.LANCZOS).convert("RGBA")
    draw = ImageDraw.Draw(label)
    draw.rectangle([0, ld * 0.70, ld, ld * 0.90], fill=BAND_COLOR)
    draw.text((ld / 2, ld * 0.80), BAND_TEXT, font=ImageFont.truetype(FONT_FILE, round(ld * 0.12)), anchor="mm", fill=VINYL)
    label.putalpha(disc_mask(ld))
    img.alpha_composite(label, ((d - ld) // 2, (d - ld) // 2))
    hole = round(d * 0.025)
    ImageDraw.Draw(img).ellipse([d / 2 - hole, d / 2 - hole, d / 2 + hole, d / 2 + hole], fill=(40, 40, 46))
    return img

def sheen():
    """Light on the vinyl that stays put while the record turns: two soft wedges and a bright rim."""
    yy, xx = np.mgrid[:d, :d] - (d - 1) / 2
    r = np.hypot(xx, yy) / (d / 2)
    a = np.arctan2(yy, xx)
    wedges = np.cos(a + math.pi / 4) ** 16
    on_vinyl = (r > LABEL + 0.02) & (r < 0.98)
    alpha = np.where(on_vinyl, 70 * wedges, 0) + np.where((r > 0.975) & (r <= 1), 90, 0)
    img = np.zeros((d, d, 4), np.uint8)
    img[..., :3] = 255
    img[..., 3] = np.clip(alpha, 0, 255).astype(np.uint8)
    return Image.fromarray(img)

def shadow():
    pad = d // 6
    img = Image.new("RGBA", (d + 2 * pad, d + 2 * pad))
    ImageDraw.Draw(img).ellipse([pad, pad + d // 30, pad + d, pad + d + d // 30], fill=(0, 0, 0, 120))
    return img.filter(ImageFilter.GaussianBlur(d // 25)), pad

def overlay(t):
    img = Image.new("RGBA", (w, h))
    x0, y0 = round(w * X - d / 2), round(h * Y - d / 2)
    img.alpha_composite(shade, (x0 - pad, y0 - pad))
    turned = disc.rotate(-360 * RPM / 60 * t, resample=Image.BICUBIC)  # negative angle turns it clockwise
    img.alpha_composite(turned, (x0, y0))
    img.alpha_composite(light, (x0, y0))
    return img

def album_art(src):
    """A square crop of one frame of the video, for the label."""
    raw = subprocess.check_output(["ffmpeg", "-v", "error", "-ss", str(ART_AT), "-i", src, "-frames:v", "1",
                                   "-f", "rawvideo", "-pix_fmt", "rgb24", "-"])
    frame = Image.frombytes("RGB", (w, h), raw)
    x0, y0, x1, y1 = ART_BOX
    return frame.crop((round(x0 * w), round(y0 * h), round(x1 * w), round(y1 * h)))

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)
d = round(w * SIZE)
disc = record(album_art(src))
light = sheen()
shade, pad = shadow()
render(src, out)
```

## More music-synced visuals

- [AI voice orb **AI voice orb**](https://www.recharm.com/effects/ai-voice-orb)
- [Audio-reactive blob **Audio-reactive blob**](https://www.recharm.com/effects/audio-reactive-blob)
- [Audio-reactive particles **Audio-reactive particles**](https://www.recharm.com/effects/audio-reactive-particles)
- [Audio-reactive text **Audio-reactive text**](https://www.recharm.com/effects/audio-reactive-text)
- [Audio waveform **Audio waveform**](https://www.recharm.com/effects/audio-waveform)

## 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)
