How to add beat-synced cuts with Claude

The picture hard-cuts on every kick of the 120 BPM drum loop, every half second, swapping between the orbit and the push-in shot, with every other pair punched 30% closer.

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

    Make beat-synced cuts from main.mp4 and broll.mp4
    Shots: main, b-roll, main 1.3x, b-roll 1.3x

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 beat-synced cuts 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
SHOTSmain, b-roll, main 1.3x, b-roll 1.3xThe shot order that repeats, one shot per beat: which clip (0 = main, 1 = b-roll) and how far to zoom in.
BPM_GUESS120Tempo the beat tracker starts from; it settles on the music's real tempo.

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
Cut main.mp4 and broll.mp4 to the beat of the music in main.mp4's audio. Use librosa in Python to find the beats (onset_strength and beat_track with hop_length 256 and start_bpm 120, then onset_backtrack so each beat sits where its drum hit starts) and round each beat time to the nearest frame at 30fps. The music is 120 BPM, so the cuts land at 0.5, 1.0, 1.5 ... 5.5 seconds. Make a hard cut (no transition) on every beat, giving one shot per beat, in this repeating order: main.mp4 full frame; broll.mp4 full frame; main.mp4 zoomed in 1.3x; broll.mp4 zoomed in 1.3x. Each shot shows its clip at the same moment it covers in the output (the shot from 0.5s to 1.0s uses broll.mp4 from 0.5s to 1.0s), so the motion stays continuous. Zoomed shots crop the center of the frame by 1.3x equally on all four sides and scale back up to full size. Keep main.mp4's size (540x960), 30fps, its 6 second length and its audio unchanged under the whole thing. Use ffmpeg for the cutting (no text is drawn; if you add any, draw it with Pillow, not ffmpeg's drawtext), and save it as output.mp4.

Run it without Claude

This is the exact script we tested. Save it as beat-synced-cuts.py and run:

python3 beat-synced-cuts.py main.mp4 broll.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Beat-synced cuts: librosa finds the drum beats in the music and the video hard-cuts to a new shot on every one.

Usage: python3 beat-synced-cuts.py main.mp4 broll.mp4 output.mp4  (the music is main.mp4's audio track)
Needs ffmpeg and librosa (pip install librosa).
"""
import json
import subprocess
import sys
import tempfile

import librosa

SHOTS = [(0, 1.0), (1, 1.0), (0, 1.3), (1, 1.3)]  # repeating shot order: (0 = main, 1 = b-roll, zoom); 1.3 crops in 30%
BPM_GUESS = 120     # where the beat tracker starts looking; it settles on the music's real tempo


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"], float(num) / float(den), float(info["format"]["duration"])


def beat_times(path, fps):
    """Seconds of each beat, moved back to where its drum hit starts and rounded to a whole frame."""
    with tempfile.NamedTemporaryFile(suffix=".wav") as wav:
        subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", path, "-vn", "-ac", "1", "-ar", "22050", wav.name], check=True)
        y, sr = librosa.load(wav.name, sr=None)
    hop = 256  # analysis step in samples (about 12ms), fine enough to place a cut on the right video frame
    strength = librosa.onset.onset_strength(y=y, sr=sr, hop_length=hop)
    _, beats = librosa.beat.beat_track(onset_envelope=strength, sr=sr, hop_length=hop, start_bpm=BPM_GUESS)
    hits = librosa.frames_to_time(librosa.onset.onset_backtrack(beats, strength), sr=sr, hop_length=hop)
    return sorted({round(t * fps) / fps for t in hits})


main, broll, out = sys.argv[1:4]
w, h, fps, duration = probe(main)
cuts = [0.0] + [t for t in beat_times(main, fps) if 0 < t < duration] + [duration]

# each shot plays its clip at the same moment it covers in the output, so the motion stays continuous
inputs, parts = [], []
for i, (start, end) in enumerate(zip(cuts, cuts[1:])):
    clip, zoom = SHOTS[i % len(SHOTS)]
    inputs += ["-ss", f"{start:.4f}", "-t", f"{end - start:.4f}", "-i", (main, broll)[clip]]
    parts.append(f"[{i + 1}:v]scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},"
                 f"crop=iw/{zoom}:ih/{zoom},scale={w}:{h},fps={fps},setsar=1[s{i}]")
parts.append("".join(f"[s{i}]" for i in range(len(parts))) + f"concat=n={len(parts)}:v=1:a=0,format=yuv420p[v]")

subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", main, *inputs, "-filter_complex", ";".join(parts),
                "-map", "[v]", "-map", "0:a", "-c:v", "libx264", "-crf", "18", "-r", str(fps), "-c:a", "copy", out],
               check=True)

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