---
title: "How to add a music player overlay with Claude (tested prompt and script)"
description: "A dark now-playing card slides up with cover art, \"Golden Hour\" by Recharm Beats, a pink progress bar creeping across and level bars bouncing on every beat. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/music-player-overlay
---

# How to add a music player overlay with Claude

A dark now-playing card slides up with cover art, "Golden Hour" by Recharm Beats, a pink progress bar creeping across and level bars bouncing on every beat.

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

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

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

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