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 music player overlay to input.mp4 Title, artist: Golden Hour, Recharm Beats
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 music player overlay 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 |
|---|---|---|
TITLE, ARTIST | Golden Hour, Recharm Beats | The track name and artist on the card. |
START | 0.4 | Second the card starts sliding up. |
Y | 0.82 | Card center, as a share of the frame height. |
ACCENT | (255, 64, 129) | Color of the progress fill and the level bars. |
COVER_AT | 1.0 | Second of the video used as the cover art. |
DECAY | 0.80 | How much of a beat's bounce the level bars keep each frame. |
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 now-playing music card to input.mp4 (540x960), with beat times in beats.json ("beats": list of seconds). The card is 475x203 pixels, color (18, 18, 22) at 88% opacity with 22-pixel rounded corners, centered across with its center 82% down the frame. It slides up from 240 pixels lower and fades in, starting at 0.4 seconds and taking 0.45 seconds with an ease-out, then stays. Inside, 19 pixels from the card's top-left: a 108-pixel square of cover art with 11-pixel rounded corners, cut as the center 324x324 square of the video's frame at 1.0 second. Right of it, starting 146 pixels into the card: the title "Golden Hour" in bold white 27-pixel text (baseline 60 pixels from the card top) and "Recharm Beats" in regular 20-pixel gray (170, 170, 178) text (baseline 95 pixels down). Below the cover, 149 pixels from the card top: a 6-pixel-tall rounded progress bar from 19 to 456 pixels across the card, gray (70, 70, 78) track and pink (255, 64, 129) fill reaching (current time / video length) of the way across, with a 12-pixel white knob at the end of the fill. Under the bar, elapsed time ("0:02") at the left and the video length ("0:06") at the right in 16-pixel gray. In the card's top-right corner, 19 pixels in: four pink (255, 64, 129) level bars 6 pixels wide and 4 pixels apart, bottom-aligned, up to 32 pixels tall; on each beat time they jump to (0.2 + 0.8 x weight) of that height, weights 0.7, 1.0, 0.5, 0.85, and between beats they fall back to 20% by keeping 80% of the bounce each frame. Draw it with Python and Pillow and put it on the video with ffmpeg (don't use 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 music-player-overlay.py and run:
python3 music-player-overlay.py input.mp4 beats.json output.mp4Show the script
#!/usr/bin/env python3
"""Music player overlay: a dark now-playing card slides up with cover art, the track title and a pink progress bar.
Usage: python3 music-player-overlay.py input.mp4 beats.json output.mp4
beats.json holds the track's beat times in seconds, for example from librosa:
{"beats": [0.0, 0.5, 1.0, ...]}
The progress bar runs from the start to the end of the video. 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
TITLE, ARTIST = "Golden Hour", "Recharm Beats"
START = 0.4 # second the card starts sliding up
SLIDE = 0.45 # seconds the slide takes
WIDTH = 0.88 # card width as a share of the frame width
Y = 0.82 # card center (0 = top, 1 = bottom), centered across
CARD = (18, 18, 22, 225) # card color and opacity
ACCENT = (255, 64, 129) # progress fill and the bouncing level bars
COVER_AT = 1.0 # second of the video used as the cover art
COVER_CROP = 0.6 # cover side as a share of the frame width, cut from the frame center
DECAY = 0.80 # how much of a beat's bounce carries over to the next frame
BOLD = ["/System/Library/Fonts/Supplemental/Arial Bold.ttf", # macOS
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", # Linux
"C:/Windows/Fonts/arialbd.ttf"] # Windows
REGULAR = ["/System/Library/Fonts/Supplemental/Arial.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"C:/Windows/Fonts/arial.ttf"]
BOLD_FILE = next((f for f in BOLD if Path(f).exists()), None) or sys.exit("No bold font found: add one to BOLD")
REGULAR_FILE = next((f for f in REGULAR if Path(f).exists()), None) or sys.exit("No font found: add one to REGULAR")
def card_base():
"""The parts of the card that don't change: background, rounded cover art, title and artist."""
cw, pad, cover = round(w * WIDTH), round(w * 0.035), round(w * 0.2)
ch = pad + cover + round(w * 0.105) + pad
img = Image.new("RGBA", (cw, ch))
draw = ImageDraw.Draw(img)
draw.rounded_rectangle([0, 0, cw - 1, ch - 1], radius=round(w * 0.04), fill=CARD)
raw = subprocess.check_output(["ffmpeg", "-v", "error", "-ss", str(COVER_AT), "-i", src, "-frames:v", "1",
"-f", "rawvideo", "-pix_fmt", "rgb24", "-"])
frame = Image.frombytes("RGB", (w, h), raw)
side = round(w * COVER_CROP)
art = frame.crop(((w - side) // 2, (h - side) // 2, (w + side) // 2, (h + side) // 2)).resize((cover, cover), Image.LANCZOS)
mask = Image.new("L", (cover, cover))
ImageDraw.Draw(mask).rounded_rectangle([0, 0, cover - 1, cover - 1], radius=round(cover * 0.1), fill=255)
img.paste(art, (pad, pad), mask)
text_x = pad * 2 + cover
draw.text((text_x, pad + cover * 0.38), TITLE, font=ImageFont.truetype(BOLD_FILE, round(w * 0.05)),
anchor="ls", fill="white")
draw.text((text_x, pad + cover * 0.70), ARTIST, font=ImageFont.truetype(REGULAR_FILE, round(w * 0.037)),
anchor="ls", fill=(170, 170, 178))
return img
def card_at(t, level):
"""The card at time t: progress bar, elapsed and total time, and level bars bouncing by `level` (0-1)."""
img = base.copy()
draw = ImageDraw.Draw(img)
cw, pad, cover = img.width, round(w * 0.035), round(w * 0.2)
bar_y, bar_h = pad + cover + round(w * 0.04), max(4, round(w * 0.011))
draw.rounded_rectangle([pad, bar_y, cw - pad, bar_y + bar_h], radius=bar_h // 2, fill=(70, 70, 78))
fill_x = pad + (cw - 2 * pad) * min(t / duration, 1)
draw.rounded_rectangle([pad, bar_y, max(fill_x, pad + bar_h), bar_y + bar_h], radius=bar_h // 2, fill=ACCENT)
draw.ellipse([fill_x - bar_h, bar_y - bar_h / 2, fill_x + bar_h, bar_y + bar_h * 1.5], fill="white")
small = ImageFont.truetype(REGULAR_FILE, round(w * 0.03))
draw.text((pad, bar_y + bar_h * 2.5), clock(t), font=small, anchor="lt", fill=(170, 170, 178))
draw.text((cw - pad, bar_y + bar_h * 2.5), clock(duration), font=small, anchor="rt", fill=(170, 170, 178))
bar_w, gap, tall = round(w * 0.012), round(w * 0.008), round(w * 0.06)
for i, weight in enumerate((0.7, 1.0, 0.5, 0.85)):
x = cw - pad - (4 - i) * (bar_w + gap)
height = tall * (0.2 + 0.8 * level * weight)
draw.rectangle([x, pad + tall - height, x + bar_w, pad + tall], fill=ACCENT)
return img
def clock(seconds):
s = int(seconds)
return f"{s // 60}:{s % 60:02d}"
def beat_levels(beats, frames):
"""Bounce (0-1) at each frame: 1 on a beat, then falling back by DECAY each frame."""
level = [0.0] * frames
for b in beats:
if 0 <= round(b * fps) < frames:
level[round(b * fps)] = 1.0
for i in range(1, frames):
level[i] = max(level[i], level[i - 1] * DECAY)
return level
def overlay(t, level):
img = Image.new("RGBA", (w, h))
if t < START:
return img
p = min((t - START) / SLIDE, 1)
ease = 1 - (1 - p) ** 3
card = card_at(t, level)
if p < 1:
card.putalpha(card.getchannel("A").point(lambda a: round(a * ease)))
y = round(h * Y - card.height / 2 + (1 - ease) * h * 0.25)
img.paste(card, ((w - card.width) // 2, y)) # paste clips the part still below the frame
return img
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."""
frames = math.ceil(duration * fps)
levels = beat_levels(json.loads(Path(beats_file).read_text())["beats"], frames)
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(frames):
ff.stdin.write(overlay(i / fps, levels[i]).tobytes())
ff.stdin.close()
if ff.wait():
sys.exit("ffmpeg failed")
src, beats_file, out = sys.argv[1:4]
w, h, rate, fps, duration = probe(src)
base = card_base()
render(src, out)





