| name | review-video-pacing-rhythm |
| description | Detect cut points (ffmpeg scene-detect) and music beats (librosa via the beat-sync-reel skill); flag cuts > 100 ms off the nearest beat and scenes > 4 s with no internal motion change. Emits a pacing-report.md with a waveform overlay PNG. |
review-video-pacing-rhythm
Purpose
Off-beat cuts are felt before they're heard — the eye registers the mismatch and the ad feels amateur. This atom is the deterministic check: every cut, every beat, every delta.
It also catches the other pacing failure mode — a scene that runs too long without internal change (static framing, no motion, no caption shift) — which kills retention.
Inputs
<video_path> (required)
<music_path> — the music bed audio (default: look in <video_folder>/audio/variants/<active>/music.mp3, fall back to extracting audio from the video)
<output_dir> — default <video_folder>/review-notes/pacing-<ts>/
<beat_tolerance_ms> — max acceptable cut-to-beat delta (default 100)
<scene_drag_threshold_s> — flag scenes longer than this with no motion change (default 4.0)
Workflow
Step 1 — Detect cuts
Use ffmpeg scene-detect filter:
ffmpeg -i <video_path> -vf "select='gt(scene,0.4)',showinfo" -f null - 2>&1 \
| grep showinfo | extract pts_time
Persist cuts.json = list of cut timestamps in seconds.
Step 2 — Detect beats
Use librosa (reuse the beat-sync-reel skill's setup — do not re-vendor):
import librosa
y, sr = librosa.load(music_path)
tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)
beat_times = librosa.frames_to_time(beat_frames, sr=sr)
Persist beats.json = list of beat timestamps in seconds, plus tempo.
Step 3 — Compute deltas
For each cut, find the nearest beat. Record delta_ms = (cut_time - nearest_beat_time) * 1000.
- Cuts within ±
beat_tolerance_ms → on-beat
- Cuts outside → off-beat (flagged)
Step 4 — Detect static-stretch
For each scene (cut-to-cut interval longer than scene_drag_threshold_s):
- Sample frames at 4 fps within the scene
- Compute mean absolute difference between consecutive frames
- If mean MAD < 2 (essentially a still frame) → flag as drag
Step 5 — Overlay PNG
Render pacing-overlay.png: x-axis = time, plot:
- Music waveform (decimated)
- Beat tick marks (vertical lines, alpha 0.3)
- Cut tick marks (vertical lines, alpha 0.8, green if on-beat, red if off-beat)
- Drag region rectangles (yellow, alpha 0.2)
Use matplotlib if available; otherwise emit a simpler ASCII summary.
Step 6 — Write report
# Pacing report — <video>
- **Tempo:** 128 BPM
- **Cuts:** 14 (12 on-beat, 2 off-beat)
- **Drag scenes:** 1
- **Overlay:** [pacing-overlay.png](./pacing-overlay.png)
## Off-beat cuts
| Cut | Time | Nearest beat | Delta | Severity |
|---|---|---|---|---|
| 7 | 8.43s | 8.59s | -160 ms | P1 |
| 11 | 15.21s | 15.00s | +210 ms | P1 |
## Drag scenes
| Scene | Range | Duration | Mean frame-diff | Note |
|---|---|---|---|---|
| 6 | 12.0–17.2s | 5.2s | 1.1 | "Static framing on bike for >5s; no caption shift" |
## Status
- ✅ APPROVED if 0 off-beat cuts AND 0 drag scenes
- ⚠ NEEDS REVISION otherwise
Output
cuts.json, beats.json
pacing-overlay.png (or pacing-overlay.txt if matplotlib unavailable)
pacing-report.md
manifest.json
Quality checks
- Tempo detected and stated; if librosa returns implausible BPM (< 40 or > 220), flag as
tempo_uncertain.
- Off-beat cuts each have a delta_ms value; sign of delta tells you whether the cut is early or late.
- Drag scenes are only flagged when duration is over threshold AND mean frame diff is low (no false-positive on long busy scenes).
Failure modes
- No music bed (talking-head ad with no music) — skip beat detection; report only drag scenes. Manifest
beat_check: skipped (no_music).
- Music drops mid-ad — beat detection still works but beats stop appearing; cuts past the music drop are unscoreable. Flag in manifest.
- Cuts are intentionally off-beat for effect — possible but rare in social ads. The skill flags; the operator decides.
- librosa not available — abort with
pip install librosa instructions. Do not silently fall back to ffmpeg-only beat detection (less accurate).
Example
review-video-pacing-rhythm
video_path=peloton/ads/video-03-ironman-comeback/edits/master-final.mp4
music_path=peloton/ads/video-03-ironman-comeback/audio/variants/B_cinematic/music.mp3
Phase 7b (creative) of the new review-video.md.