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 stacked ridgeline waveform to input.mp4 Second the ridgelines fade in: 0.5 How many lines are stacked and the pixels between them: 16, 11
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 stacked ridgeline waveform 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 |
|---|---|---|
START | 0.5 | Second the ridgelines fade in. |
ROWS, GAP | 16, 11 | How many lines are stacked and the pixels between them. |
LAG | 2 / 30 | Seconds of sound between one line and the next. Longer keeps more history on screen. |
PEAK | 64 | Tallest ridge in pixels. |
FRONT | 0.94 | Baseline of the front line, 0-1 down the frame. |
SPREAD | 0.76 | Share of the width the ridges use; the ends stay flat. |
LINE | (255, 255, 255) | Line color. |
SHADE | 230 | Opacity (0-255) of the black panel. |
RANGE | 45 | Loudness range shown, in dB. Lower keeps only the loudest frequencies. |
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 stacked ridgeline waveform (the Joy Division album cover look) to input.mp4 (540x960, 30fps, with a drum track). From 0.5 seconds, fading in over 0.3 seconds, cover the frame from y=657 to the bottom with a full-width black panel at 230/255 opacity. On it draw 16 white (255, 255, 255) lines, 2 pixels wide, each running the full width: the front line's baseline is at y=902 and each line behind it sits 11 pixels higher. The front line shows the sound right now and each line further back shows the sound 2 frames (1/15 second) earlier, so the ridges move up and back through the stack. For each line take the last 0.05 seconds of audio up to its moment, apply a Hann window and an FFT, and read the magnitude at 40 log-spaced frequencies from 50 Hz to 10 kHz. Convert to dB relative to the loudest band anywhere in the clip, map -45 dB to 0 and 0 dB to 1 (clip below 0), and mirror the 40 values so the bass is in the middle of the line and the highs run out to both ends. Spread them over the middle 76% of the width (from x=65 to x=475), multiply by sin(pi*p)^2 where p goes 0 to 1 across that span so the ends taper to flat, and raise the line by up to 64 pixels. Lines whose moment is before 0.5 seconds stay flat, and in silence a line is flat. Draw the lines back to front and fill below each line with the panel's black so it hides the lines behind it. Nothing is drawn before 0.5 seconds and nothing above the panel. Use Python with numpy and Pillow to read the audio and draw the lines and ffmpeg to put them on the video (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 ridgeline-waveform.py and run:
python3 ridgeline-waveform.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Ridgeline waveform: stacked white lines on a dark panel ripple with the soundtrack, the newest at the front.
Usage: python3 ridgeline-waveform.py input.mp4 output.mp4 (the input needs an audio track)
Needs ffmpeg, Pillow and numpy (pip install pillow numpy).
"""
import json
import math
import subprocess
import sys
import numpy as np
from PIL import Image, ImageDraw
START = 0.5 # second the ridgelines fade in
FADE = 0.3 # seconds the fade-in takes
ROWS = 16 # number of stacked lines
GAP = 11 # pixels between neighboring lines
LAG = 2 / 30 # seconds of sound between one line and the next; the front line is the newest
PEAK = 64 # tallest ridge in pixels
FRONT = 0.94 # baseline of the front (lowest) line, 0-1 down the frame
SPREAD = 0.76 # share of the panel width the ridges use; the ends stay flat
BANDS = 40 # frequency bands across each half of a line (bass in the middle)
LINE = (255, 255, 255) # line color
WIDTH = 2 # line width in pixels
SHADE = 230 # opacity (0-255) of the dark panel
RATE = 48000 # audio samples per second read from the input
SLICE = 0.05 # seconds of sound behind each line
RANGE = 45 # loudness range shown, in dB: lower keeps only the loudest bands
def audio(path):
"""The soundtrack as mono floats from -1 to 1."""
raw = subprocess.check_output(["ffmpeg", "-v", "error", "-i", path, "-f", "s16le", "-ac", "1", "-ar", str(RATE), "-"])
return np.frombuffer(raw, np.int16).astype(np.float32) / 32768
def spectrum(t):
"""Loudness (0-1) of each frequency band, 50 Hz to 10 kHz, in the SLICE of sound ending at t."""
n = round(SLICE * RATE)
a = round(t * RATE) - n
chunk = np.zeros(n)
if a + n > 0:
part = sound[max(a, 0):a + n]
chunk[n - len(part):] = part
mags = np.abs(np.fft.rfft(chunk * np.hanning(n)))
freqs = np.fft.rfftfreq(n, 1 / RATE)
return np.interp(np.geomspace(50, 10000, BANDS), freqs, mags)
def ridge(t):
"""Heights in pixels for each column of one line: the spectrum mirrored around the middle, tapering to flat ends."""
db = 20 * np.log10(np.maximum(spectrum(t) / loudest, 1e-9))
bands = np.clip(1 + db / RANGE, 0, 1) # 0 at RANGE dB below the loudest moment, 1 at it
half = np.concatenate([bands[::-1], bands]) # bass in the middle, highs toward both ends
x = np.linspace(0, 1, w)
inner = np.clip((x - (1 - SPREAD) / 2) / SPREAD, 0, 1)
heights = np.interp(inner, np.linspace(0, 1, len(half)), half)
taper = np.sin(np.pi * inner) ** 2 # ridges rise toward the middle like a mountain range
return heights * taper * PEAK
def panel(t):
"""The dark panel with ROWS lines, drawn back to front so each line hides the ones behind it."""
top = round(h * FRONT - (ROWS - 1) * GAP - PEAK - 16)
img = Image.new("RGBA", (w, h - top), (0, 0, 0, SHADE))
draw = ImageDraw.Draw(img)
for k in reversed(range(ROWS)):
base = h * FRONT - top - k * GAP
moment = t - k * LAG
heights = ridge(moment) if moment >= START else np.zeros(w)
points = [(x, base - y) for x, y in enumerate(heights)]
draw.polygon(points + [(w - 1, img.height), (0, img.height)], fill=(0, 0, 0, SHADE))
draw.line(points, fill=LINE + (255,), width=WIDTH, joint="curve")
return img, top
def overlay(t):
img = Image.new("RGBA", (w, h))
fade = min(max((t - START) / FADE, 0), 1)
if fade > 0:
p, top = panel(t)
p.putalpha(p.getchannel("A").point(lambda a: round(a * fade)))
img.alpha_composite(p, (0, top))
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."""
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)
sound = audio(src)
loudest = max(max(spectrum(i / 20).max() for i in range(1, math.ceil(duration * 20) + 1)), 1e-6)
render(src, out)





