---
title: "How to add a pulsing cover art with Claude (tested prompt and script)"
description: "A white-framed square cover of the product sits over a blurred, dimmed copy of the video and swells 15% on every kick drum before easing back. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/pulsing-cover-art
---

# How to add a pulsing cover art with Claude

A white-framed square cover of the product sits over a blurred, dimmed copy of the video and swells 15% on every kick drum before easing back.

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 pulsing cover art to input.mp4
   Cover width at rest as a share of the frame width: 0.60
   How much bigger the cover gets on a full-strength kick: 0.15
   ```

## 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 pulsing cover art 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                                                  |
| ------------------ | --------------- | ------------------------------------------------------------- |
| `SIZE`             | `0.60`          | Cover width at rest as a share of the frame width.            |
| `PULSE`            | `0.15`          | How much bigger the cover gets on a full-strength kick.       |
| `RELEASE`          | `0.80`          | Share of the swell kept each frame, so it eases back.         |
| `LOW_HZ, HIGH_HZ`  | `30, 150`       | Bass band that drives the pulse.                              |
| `COVER_AT`         | `1.0`           | Second of the video used as the cover when no image is given. |
| `BLUR, DIM, VIVID` | `30, 0.55, 2.0` | Background blur radius, brightness and saturation.            |

## Without the skill

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

```
Make a pulsing cover art video from input.mp4 (it has a drum loop). Background: the video itself with a 30 pixel box blur, saturation doubled and brightness multiplied by 0.55. Cover: a square crop from the center of the video's frame at 1.0 second, drawn 60% of the frame width across (white frame included) with a white (255, 255, 255) border 2.5% of that width and a soft dark drop shadow, centered on the frame. Drive its size from the bass: for each video frame take an FFT of the audio around it, sum the 30-150 Hz band, divide by the loudest frame's value, and let it fall back by multiplying by 0.8 each frame instead of dropping at once; scale the cover by 1 + 0.15 times that, about its center, so it is 15% bigger on each kick and back to its rest size between kicks. Use Python with Pillow and numpy to build the frames and ffmpeg to put them on the video, keep the audio, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `pulsing-cover-art.py` and run:

```
python3 pulsing-cover-art.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Pulsing cover art: a white-framed square cover swells on every kick drum and eases back, over a blurred copy of the video.

Usage: python3 pulsing-cover-art.py input.mp4 [cover.png] output.mp4  (the input needs an audio track;
without cover.png, a square from the video at COVER_AT seconds is the cover)
Needs ffmpeg, numpy and Pillow (pip install numpy pillow).
"""
import json
import math
import subprocess
import sys

import numpy as np
from PIL import Image, ImageDraw, ImageFilter

SIZE = 0.60                 # cover width at rest as a share of the frame width, white frame included
X, Y = 0.5, 0.5             # cover center (0-1 across and down the frame)
PULSE = 0.15                # how much bigger the cover gets on a full-strength kick (0.15 = 15%)
RELEASE = 0.80              # how much of the swell carries over to the next frame, so it eases back
LOW_HZ, HIGH_HZ = 30, 150   # the bass band that drives the pulse
BORDER = 0.025              # white frame width as a share of the cover width
COVER_AT = 1.0              # second of the video used as the cover when no image is given
BLUR = 30                   # background blur radius in pixels
DIM = 0.55                  # background brightness (1 = unchanged)
VIVID = 2.0                 # background saturation (1 = unchanged), so the blur keeps its color
RATE = 48000                # audio sample rate the analysis uses

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 bass_per_frame(src, frames):
    """Bass loudness (0-1) at each video frame: jumps up on a kick, then falls back by RELEASE each frame."""
    pcm = subprocess.check_output(["ffmpeg", "-v", "error", "-i", src, "-ac", "1", "-ar", str(RATE), "-f", "f32le", "-"])
    audio = np.frombuffer(pcm, np.float32)
    n = 2048
    window = np.hanning(n)
    freqs = np.fft.rfftfreq(n, 1 / RATE)
    band = (freqs >= LOW_HZ) & (freqs < HIGH_HZ)
    padded = np.pad(audio, (n // 2, n))
    level = np.array([np.abs(np.fft.rfft(padded[round(i / fps * RATE):][:n] * window))[band].sum()
                      for i in range(frames)])
    level /= max(level.max(), 1e-9)
    for i in range(1, frames):
        level[i] = max(level[i], level[i - 1] * RELEASE)
    return level

def cover_art(path):
    """The cover as a square RGBA image with a white frame and a soft drop shadow around it."""
    if path:
        img = Image.open(path).convert("RGB")
    else:
        raw = subprocess.check_output(["ffmpeg", "-v", "error", "-ss", str(COVER_AT), "-i", src, "-frames:v", "1",
                                       "-f", "rawvideo", "-pix_fmt", "rgb24", "-"])
        img = Image.frombytes("RGB", (w, h), raw)
    side = min(img.size)
    img = img.crop(((img.width - side) // 2, (img.height - side) // 2,
                    (img.width + side) // 2, (img.height + side) // 2))
    d = round(w * SIZE)
    b = round(d * BORDER)
    framed = Image.new("RGB", (d, d), "white")
    framed.paste(img.resize((d - 2 * b, d - 2 * b), Image.LANCZOS), (b, b))
    pad = d // 8
    shadow = Image.new("RGBA", (d + 2 * pad, d + 2 * pad))
    ImageDraw.Draw(shadow).rectangle([pad, pad + d // 40, pad + d, pad + d + d // 40], fill=(0, 0, 0, 160))
    card = shadow.filter(ImageFilter.GaussianBlur(d // 30))
    card.paste(framed, (pad, pad))
    return card

def overlay(level):
    img = Image.new("RGBA", (w, h))
    scale = 1 + PULSE * level
    c = card.resize((round(card.width * scale), round(card.height * scale)), Image.LANCZOS)
    img.alpha_composite(c, (round(w * X - c.width / 2), round(h * Y - c.height / 2)))
    return img

def render(src, out):
    """Blur and dim the video, draw the pulsing cover on every frame and save to out, keeping the audio."""
    frames = math.ceil(duration * fps)
    bass = bass_per_frame(src, 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", f"[0:v]boxblur={BLUR},eq=saturation={VIVID},colorchannelmixer=rr={DIM}:gg={DIM}:bb={DIM}[bg];"
                                              "[bg][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(bass[i]).tobytes())
    ff.stdin.close()
    if ff.wait():
        sys.exit("ffmpeg failed")

src, *cover_file, out = sys.argv[1:]
w, h, rate, fps, duration = probe(src)
card = cover_art(cover_file[0] if cover_file else None)
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)
