help quicky produce instagram reels and youtube shorts
80
93%
Does it follow best practices?
Impact
47%
1.95xAverage score across 1 eval scenario
Passed
No known issues
#!/usr/bin/env python3
"""Propose a beat-synced cut plan from normalized clips + beat data.
Pacing model — phase-based, all durations snapped to actual beat timestamps:
HOOK ~2 bars hold grab attention; most kinetic clip first
ESTABLISH 4-beat cuts let shots read; set the scene
GROOVE 2-beat cuts settle into rhythm
PUNCH 2-beat cuts energy peak (1-beat only via --punch-beats 1 —
sub-second cuts read as "busy", so opt-in)
BREATHE 4-beat cuts relief after the peak
BUILD 2-beat cuts re-accelerate into the ending
CLOSE ~2 bars hold payoff / smile / CTA
Guarantees:
- minimum cut length is 2 beats (never the 1-beat flicker) unless --punch-beats 1
- in-points are spread across each clip's FULL duration (usage N of K starts
~N/K of the way through the clip), not clustered in the first few seconds
- two adjacent segments never come from the same clip (that's a jump cut)
The output plan/cut_plan.json is meant to be HUMAN-EDITED before rendering.
Every field is safe to change: reorder segments, change "in" points, durations
(renderer re-snaps audio automatically), swap clip paths.
Usage:
build_cut_plan.py work/clips.json work/beats.json --target-duration 30 \
--mezz-dir work/mezz --out plan/cut_plan.json \
[--order clipA,clipB,...] [--hook clipC] [--close clipD] [--punch-beats 1]
"""
import argparse, json, sys
from pathlib import Path
# (name, beats_per_cut_multiplier, share of body time). Multiplier is applied
# to the minimum cut length: with the default 2-beat minimum, establish/breathe
# are 4-beat and groove/punch/build are 2-beat.
BODY_PHASES = [
("establish", 2, 0.22),
("groove", 1, 0.24),
("punch", 0, 0.16), # 0 = punch_beats (the fastest allowed cut)
("breathe", 2, 0.16),
("build", 1, 0.22),
]
def main():
ap = argparse.ArgumentParser()
ap.add_argument("clips_json")
ap.add_argument("beats_json")
ap.add_argument("--mezz-dir", default="work/mezz")
ap.add_argument("--target-duration", type=float, default=30)
ap.add_argument("--out", default="plan/cut_plan.json")
ap.add_argument("--order", help="Comma-separated clip stems to force rotation order")
ap.add_argument("--hook", help="Clip stem to open with (pick from contact sheets)")
ap.add_argument("--close", help="Clip stem to end on (pick from contact sheets)")
ap.add_argument("--min-beats", type=int, default=2,
help="Minimum cut length in beats outside the punch phase (default 2)")
ap.add_argument("--punch-beats", type=int, default=None,
help="Cut length during the punch phase (default = --min-beats; "
"pass 1 for sub-second flash cuts — use sparingly)")
args = ap.parse_args()
punch_beats = args.punch_beats or args.min_beats
clips = [c for c in json.loads(Path(args.clips_json).read_text())["clips"]
if "error" not in c]
if not clips:
sys.exit("No usable clips in clips.json")
beats = json.loads(Path(args.beats_json).read_text())
beat_times = [0.0] + beats["beats"]
mezz = Path(args.mezz_dir)
def mezz_path(c):
return str(mezz / (Path(c["file"]).stem + ".mp4"))
by_stem = {Path(c["file"]).stem: c for c in clips}
if args.order:
clips = [by_stem[s] for s in args.order.split(",") if s in by_stem]
if args.hook and args.hook in by_stem:
clips = [by_stem[args.hook]] + [c for c in clips if c is not by_stem[args.hook]]
close_clip = by_stem.get(args.close) if args.close else None
spb = 60.0 / beats["bpm"] # seconds per beat
hold_beats = 8 if args.target_duration >= 20 else 4 # hook/close holds
hold_beats = min(hold_beats, max(2, (len(beat_times) - 1) // 4))
# --- Pass 1: lay out (phase, beat-count) slots against the beat grid ---
def beats_for(phase_mult):
return punch_beats if phase_mult == 0 else args.min_beats * phase_mult
body_target = max(0.0, args.target_duration - 2 * hold_beats * spb)
slots = [("hook", hold_beats)]
body_elapsed, i = 0.0, hold_beats
while body_elapsed < body_target and i < len(beat_times) - 1:
frac, cum = body_elapsed / body_target if body_target else 1.0, 0.0
phase = BODY_PHASES[-1]
for p in BODY_PHASES:
cum += p[2]
if frac < cum:
phase = p
break
n = beats_for(phase[1])
end_idx = min(i + n, len(beat_times) - 1)
dur = beat_times[end_idx] - beat_times[i]
if dur <= 0.05:
break
slots.append((phase[0], end_idx - i))
body_elapsed += dur
i = end_idx
close_end = min(i + hold_beats, len(beat_times) - 1)
if beat_times[close_end] - beat_times[i] > 0.5:
slots.append(("close", close_end - i))
# --- Pass 2: assign a clip to each slot (rotation, no same-clip adjacency) ---
durations, t_idx = [], 0
for _, n in slots:
durations.append(round(beat_times[t_idx + n] - beat_times[t_idx], 3))
t_idx += n
assigned, prev, ci = [], None, 0
for k, (phase, _) in enumerate(slots):
dur = durations[k]
pick = None
if phase == "close" and close_clip and close_clip["duration"] >= dur + 0.1 \
and (close_clip is not prev or len(clips) == 1):
pick = close_clip
else:
for probe in range(len(clips)):
cand = clips[(ci + probe) % len(clips)]
if cand is prev and len(clips) > 1:
continue # never jump-cut within a clip
if cand["duration"] >= dur + 0.1:
pick = cand
ci = (ci + probe + 1) % len(clips)
break
if pick is None: # nothing long enough: allow repeat
pick = max((c for c in clips if c is not prev), default=clips[0],
key=lambda x: x["duration"])
durations[k] = dur = min(dur, round(pick["duration"] - 0.1, 3))
assigned.append(pick)
prev = pick
# --- Pass 3: spread in-points across each clip's full duration ---
usage = {}
for k, c in enumerate(assigned):
usage.setdefault(id(c), (c, []))[1].append(k)
in_points = [0.0] * len(slots)
for c, ks in usage.values():
for j, k in enumerate(ks): # usage j of len(ks), chronological
window = (c["duration"] - durations[k] - 0.1) / len(ks)
in_points[k] = round(max(0.0, j * max(window, 0.0)), 2)
NOTES = {"hook": "HOOK — most kinetic shot here",
"close": "CLOSE — payoff / smile / CTA"}
segments = [{"clip": mezz_path(c), "in": in_points[k], "duration": durations[k],
"note": NOTES.get(slots[k][0], f"{slots[k][0]} · {slots[k][1]}-beat")}
for k, c in enumerate(assigned)]
total = round(sum(s["duration"] for s in segments), 2)
plan = {
"music": beats["audio"],
"music_start": 0.0,
"bpm": beats["bpm"],
"total_duration": total,
"segments": segments,
}
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(plan, indent=2))
print(f"Cut plan: {len(segments)} segments, {total:.1f}s @ {beats['bpm']} BPM -> {out}\n")
cum = 0.0
for n, s in enumerate(segments):
print(f" {n:2d} {cum:6.2f}s {s['duration']:5.2f}s "
f"{Path(s['clip']).stem:<24} in={s['in']:<7} {s['note']}")
cum += s["duration"]
print("\nEdit plan/cut_plan.json (or ask for changes), then render a preview.")
if __name__ == "__main__":
main()