Cut clips to the bar grid
Give it the song, its beats.json and one clip per shot with the bars it covers; get
the cut with the song underneath, every shot exactly the frames between its bars. Cuts
land on the music by construction and the total never drifts, however many shots. To
trim a single clip to a frame count, use ffmpeg-frame-exact-trim.
Inputs
beats.jsonfrompython-map-song-bars(itsdownbeats).- The song WAV the beats came from, which sets the cut's length. Cut to the WAV, not the source file (a WAV was 0.19 s shorter than its M4A reported).
- One clip per shot, untrimmed, longer than its slot where possible. Shots must meet: each shot's end bar is the next one's start bar.
- Bars: -1 is the song's start (an intro before the first downbeat), a fraction is part of a bar (40.5 is beat 3 of bar 40), past the last downbeat is the song's end.
Run it
Write references/cut_to_bar_grid.md's script into the job's scripts/ as
cut_to_bar_grid.py, unchanged. Put the shots in a file, one --shot START END CLIP
per line, with any options:
--shot -1 1 video/shot01_a.mp4
--shot 1 2 video/shot02_b.mp4
--shot 2 2.5 video/shot03_a.mp4
--shot 2.5 3 video/shot04_a.mp4
--twos 3 4
--start 2=0.4
exec("python", ["scripts/cut_to_bar_grid.py", "--beats", "analysis/beats.json",
"--song", "analysis/song.wav", "--out", "cut/pass_a/cut_v1.mp4", "@cut/shots.args",
"--check"], cwd="<job>", skill="python-cut-to-bar-grid")
--twos N…: shots (1-based, in file order) stepped on twos, every frame held twice, for one look or world animated at 12 fps.--start N=SECONDS: skip shot N's idle opening (read the strip inffmpeg-frame-exact-trimto find it).--fps(24) and--size(1920x1080; clips are scaled to fill and centre-cropped).- Paths in the args file can't contain spaces.
What you get
- The cut: H.264 at
--fps, the song from the first shot's start bar to the last shot's end bar, AAC 256k. Each shot is encoded at CRF 16 on its own and joined without re-encoding: a review and rough-cut file. Deliver from the master perguide-prepare-renders-for-delivery. - One line per shot whose clip is shorter than its slot: it holds the last frame.
- With
--check:cuts checked: 83 of 83, more than a frame off: none, or the shots that missed, with their frame and how far off.
Tips and traps
- Render each shot at least as long as its slot and let the cut trim. H3 Max's
minimum is 0.92 s, so render a half-bar shot at 24 frames, or its slot's frames when
that is more (
h3-max-image-to-video), and let the cut take what it needs. A clip shorter than its slot holds its last frame, which reads as a freeze. - Build the cut so changes are a re-run. Dropping a scene or trimming the intro
means editing
shots.args(fold the bars into a neighbour, or start the first shot later) and running again, never re-rendering. - A dropped shot leaves a gap and the script stops: shots must tile the bars.
- Count frames, not seconds: each shot is
round(end_time × fps) − round(start_time × fps), so the parts add up to the song exactly. --checkuses a 0.1 scene score. Two shots that look alike (the same set, a slow dissolve in the render) can score under it and show up as "off": look at that cut before believing it (ffmpeg-scene-cutsreads the scores).
Check it
--checkon every cut. The worked job, a 3:22 song in 84 shots: 83 of 83 cuts within a frame of their bars.- The frame count printed equals the song's span at the frame rate.
- Watch the cut with sound at a few downbeats: the picture changes on the "one".


