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 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.
Show the prompt
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.mp4Show the script
#!/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)





