SuperAPI AI Directory

extract-beat-grid

Design & creation

View repository

Updated 10-15 21:54

About this skill

extract-beat-grid

Purpose

Analyse an audio or video file and produce a beat-grid.json that encodes the full rhythmic skeleton of the track: BPM, individual beat times, downbeats, bars, and onset transients. Downstream atoms consume this file to snap cut points, caption transitions, or SFX to the musical grid — replacing the previously-missing beat-sync-reel dependency that several skills referenced but no in-tree atom implemented.

If the source is a video file, the audio stream is extracted to a temporary WAV first (via ffmpeg); the atom is otherwise identical for audio-only inputs.

Inputs

Field Required Notes
source yes Path to an audio file (mp3, wav, m4a, aac, flac) or video file (mp4, mov, webm).
output_dir yes Directory where beat-grid.json and manifest.json are written. Created if absent.
beats_per_bar no, default 4 Time signature numerator — how many beats constitute one bar.

CLI:

python3 skills/atoms/audio-editing/extract-beat-grid/scripts/extract_beats.py \
  --source <path> --output-dir <dir> [--beats-per-bar 4]

Workflow

  1. Validate that source exists and ffmpeg/ffprobe are on PATH.
  2. If the source is a video file (detected by extension or by probing), extract the audio stream to a temp WAV with ffmpeg (-vn -ar 44100 -ac 1).
  3. Load audio with librosa.load() (mono, native sample rate).
  4. Run librosa.beat.beat_track() → tempo (BPM float), beat_frames array.
  5. Convert beat_frames → beat_times (seconds) with librosa.frames_to_time().
  6. Run librosa.onset.onset_detect(units="time") → onset_times array.
  7. Group beat_times into bars of beats_per_bar; first beat of each group = downbeat. Build a bars array of {index, start, end} (end = start of next bar, or track duration for the last bar).
  8. Write beat-grid.json (schema below).
  9. Write manifest.json (standard skill manifest schema).
  10. Clean up temp WAV if one was created.

beat-grid.json schema

{
  "schema_version": "1.0",
  "source": "<absolute path>",
  "duration_s": 120.4,
  "bpm": 128.0,
  "beat_interval_s": 0.469,
  "beats": [0.46, 0.93, 1.40, ...],
  "downbeats": [0.46, 2.32, 4.18, ...],
  "bars": [
    {"index": 0, "start": 0.46, "end": 2.32},
    {"index": 1, "start": 2.32, "end": 4.18},
    ...
  ],
  "onsets": [0.03, 0.46, 0.82, ...]
}

Output

  • <output_dir>/beat-grid.json — the rhythmic skeleton of the track.
  • <output_dir>/manifest.json — skill run metadata (standard manifest schema).

Quality Checks

  • beat-grid.json is valid JSON with all required top-level keys.
  • bpm is a positive float; beats is non-empty.
  • downbeats length equals ceil(len(beats) / beats_per_bar) (±1 for edge truncation).
  • bars length equals len(downbeats); each bar's end equals the next bar's start.
  • duration_s matches the source file's ffprobe duration within ±0.5s.
  • manifest.json exists with status: "pass" and output_files listing beat-grid.json.

Failure Modes

  • Source not found — script exits with a clear error before loading anything.
  • Video with no audio stream — ffmpeg extraction fails; the atom prints an error and exits non-zero. Supply an audio-only file or a video that has audio.
  • librosa not installed — install with pip3 install librosa; the script will print an actionable error message and exit with code 2 if librosa is missing.
  • soundfile / audioread backend missing — librosa needs at least one audio backend. Install with pip3 install soundfile (recommended) or pip3 install audioread.
  • Very short or silent audio — beat_track may return an empty beat array or a nonsensical BPM. The script records a warning in the manifest and still writes the (possibly sparse) JSON; callers should treat a bpm < 20 result as unreliable.
  • ffmpeg not on PATH — install via brew install ffmpeg (macOS) or apt install ffmpeg (Linux).