How to add a spectrum bars visualizer with Claude

Twenty-four glowing pink bars along the bottom jump with the music: the bass bars on the left leap on each kick drum and the bars on the right flick up with the hi-hats.

Get this effect

  1. Switch to Code in your Claude Desktop App

  2. 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/skills
  3. Ask for the effect

    Add a spectrum bars visualizer to input.mp4
    Number of bars, low notes on the left: 24
    Frequency range the bars cover: 40, 12000

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 spectrum bars visualizer 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.

SettingDefaultWhat it does
BARS24Number of bars, low notes on the left.
LOW_HZ, HIGH_HZ40, 12000Frequency range the bars cover.
BASE0.92Bottom of the bars, as a share of the frame height.
MAX_HEIGHT0.20Tallest bar as a share of the frame height.
COLOR(255, 60, 170)Bar color.
RANGE36Decibels between an empty bar and a full one.
FALL0.80Share of its height a bar keeps each frame as the sound drops.

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 spectrum bars visualizer to input.mp4, driven by its own audio. Draw 24 bars in pink (255, 60, 170) in a row from 6% to 94% across the frame, each taking 65% of its slot with rounded ends, standing on a baseline 92% of the way down and at most 20% of the frame height tall, with a soft 10-pixel glow. Bar heights come from an FFT of the audio: for each video frame take a 4096-sample Hann window centered on that frame's time, split 40 Hz to 12 kHz into 24 log-spaced bands (low notes on the left), convert each band's average magnitude to decibels, and scale each band so its 99th-percentile level over the clip is full height and 36 dB below that is empty. Bars rise instantly and fall back by keeping 80% of their height per frame; an empty bar still shows as a small dot. Use Python with numpy for the FFT and Pillow to draw the bars, and ffmpeg to put them 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 spectrum-bars-visualizer.py and run:

python3 spectrum-bars-visualizer.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Spectrum bars visualizer: a row of glowing pink bars along the bottom jumps with each frequency of the music.

Usage: python3 spectrum-bars-visualizer.py input.mp4 output.mp4
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

BARS = 24                    # number of bars, low notes on the left
LOW_HZ, HIGH_HZ = 40, 12000  # frequency range the bars cover
LEFT, RIGHT = 0.06, 0.94     # where the row starts and ends (0-1 across the frame)
BASE = 0.92                  # bottom of the bars (0-1 down the frame)
MAX_HEIGHT = 0.20            # tallest bar as a share of the frame height
GAP = 0.35                   # space between bars as a share of each bar's slot
COLOR = (255, 60, 170)       # bar color
GLOW = 10                    # glow blur radius in pixels
RANGE = 36                   # decibels between an empty bar and a full one
FALL = 0.80                  # share of its height a bar keeps each frame when the sound drops
RATE = 48000                 # audio sample rate used for the analysis


def spectrum(src):
    """Height (0-1) of every bar on every frame, from an FFT of the audio around that frame."""
    pcm = subprocess.check_output(["ffmpeg", "-v", "error", "-i", src, "-vn", "-ac", "1", "-ar", str(RATE),
                                   "-f", "f32le", "-"])
    audio = np.frombuffer(pcm, np.float32)
    size = 4096
    window = np.hanning(size)
    freqs = np.fft.rfftfreq(size, 1 / RATE)
    edges = np.geomspace(LOW_HZ, HIGH_HZ, BARS + 1)
    bands = [(freqs >= lo) & (freqs < hi) for lo, hi in zip(edges, edges[1:])]
    padded = np.concatenate([np.zeros(size), audio, np.zeros(size)])
    levels = []
    for i in range(frames):
        mid = size + round(i / fps * RATE)
        mag = np.abs(np.fft.rfft(padded[mid - size // 2:mid + size // 2] * window))
        levels.append([20 * np.log10(mag[b].mean() + 1e-9) for b in bands])
    db = np.array(levels)
    top = np.percentile(db, 99, axis=0)  # each bar scaled to its own loudest moments, so the highs move too
    heights = np.clip((db - (top - RANGE)) / RANGE, 0, 1)
    for i in range(1, frames):  # rise at once, fall back gently
        heights[i] = np.maximum(heights[i], heights[i - 1] * FALL)
    return heights


def overlay(levels):
    img = Image.new("RGBA", (w, h))
    draw = ImageDraw.Draw(img)
    slot = (RIGHT - LEFT) * w / BARS
    bar_w = slot * (1 - GAP)
    base = BASE * h
    for i, level in enumerate(levels):
        x0 = LEFT * w + i * slot + (slot - bar_w) / 2
        top = base - max(bar_w, level * MAX_HEIGHT * h)
        draw.rounded_rectangle([x0, top, x0 + bar_w, base], radius=bar_w / 2, fill=COLOR)
    glow = img.filter(ImageFilter.GaussianBlur(GLOW))
    return Image.alpha_composite(glow, 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, heights):
    """Draw the bars 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 levels in heights:
        ff.stdin.write(overlay(levels).tobytes())
    ff.stdin.close()
    if ff.wait():
        sys.exit("ffmpeg failed")


src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
frames = math.ceil(duration * fps)
render(src, out, spectrum(src))

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