CtrlK
BlogDocsLog inGet started
Tessl Logo

gamussa/reels-producer-skill

help quicky produce instagram reels and youtube shorts

94

2.12x
Quality

96%

Does it follow best practices?

Impact

87%

2.12x

Average score across 3 eval scenarios

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

montage-cuts.mdskills/reel-builder/references/

Montage Cut Plans (music-video format)

Read this when the project mode is music-video and you are building or editing plan/cut_plan.json. Talking-head plans follow references/talking-head.md instead.

Generating

python3 scripts/build_cut_plan.py work/clips.json work/beats.json \
  --target-duration 30 --hook run_01 --close finish_04 --out plan/cut_plan.json

--hook and --close take the picks from the contact-sheet review.

Phase pacing

The generator uses a phase-based structure, every duration snapped to a real beat timestamp:

PhaseCut lengthPurpose
HOOK~2-bar holdgrab attention (the contact-sheet pick, via --hook)
ESTABLISH4-beatlet shots read, set the scene
GROOVE2-beatsettle into rhythm
PUNCH2-beat (1 via --punch-beats 1)energy peak — kept short; walls of sub-second cuts read as "busy"
BREATHE4-beatrelief after the peak
BUILD2-beatre-accelerate into the ending
CLOSE~2-bar holdpayoff / smile / CTA (--close)

Even a 1-beat punch phase reads as busy to viewers — resist sub-second cuts.

Built-in guarantees: a 2-beat minimum cut (1-beat flash cuts are opt-in and punch-phase only), in-points spread across each clip's full duration rather than clustered in its first seconds, and no two adjacent segments from the same clip — that last one is what separates a montage from a jump-cut generator.

Plan shape

plan/cut_plan.json is the single human-editable source of truth:

{
  "music": "music/track.mp3",
  "music_start": 0.0,
  "segments": [
    {"clip": "work/mezz/run_01.mp4", "in": 2.4, "duration": 1.85, "note": "HOOK — most kinetic shot here"},
    {"clip": "work/mezz/gym_03.mp4", "in": 7.4, "duration": 0.94, "note": "groove · 2-beat"}
  ]
}

Hook advice: the first 1.5–2s decides scroll-past.

Starting the track on a lyric

fetch_music.py saves an .srt beside the track when the video has subtitles. To open the reel on a specific line:

python3 scripts/build_cut_plan.py work/clips.json work/beats.json \
    --music-start-lyric "fire in my bones" --out plan/cut_plan.json

The lyric timestamp is where the singer started, which is off-grid; it snaps to the nearest beat, preferring a downbeat when one is about as close. A line that appears more than once is refused rather than guessed — use --lyric-occurrence N. --music-start SECONDS snaps a raw time the same way.

Auto-generated captions mishear a lot, so check the .srt if a line is not found.

Ordering and in-points

Clips are picked least-used-first, scanned in a per-phase order. Both matter: least-used alone reproduces strict rotation exactly (with no adjacent repeats and even usage there is only one cycle), which is what made long reels sound like a loop. Reshuffling the pool per phase breaks that while keeping usage even. The shuffle is seeded, so the same inputs give the same plan.

--climax CLIP pins a clip at the energy peak, the way --hook and --close pin the ends.

In-points are positional by default: usage j of K starts about j/K of the way through the clip.

--score-in-points tries several candidates per segment and keeps the one that actually contains a person, using Apple Vision through scripts/detect_subjects.py.

This replaced an edge-density heuristic that did not work. On real go-kart 4K, bare track scored 6.3-12.6 on edge density against 8.6-12.4 for helmet close-ups — the ranges overlap completely, because tarmac carries gravel, kerbs, fences and a horizon. Vision separates the same frames with no overlap: zero detections on every empty frame, 0.65-0.75 confidence on every subject.

Costs roughly half a second per candidate and needs pip install pyobjc-framework-Vision pyobjc-framework-Quartz (macOS only). Without it the flag exits with that instruction rather than silently degrading.

It detects people, not interest. POV and scenery footage scores zero throughout, which is correct — the shot is the motion, not a subject. The "no person in any candidate" warning is only meaningful for footage that should show someone.

Reframing a reused clip

--pan on normalize_clips.py is per CLIP, so a clip reused at several in-points gets one framing for all of them — wrong whenever the subject sits on a different side each time.

For per-cut framing, keep the width at normalize time and crop at render:

python3 scripts/normalize_clips.py work/clips.json --outdir work/mezz --no-crop

Then give individual segments a "pan": left, center, right, or a 0..1 fraction.

{"clip": "work/mezz/drone.mp4", "in": 12.4, "duration": 1.9, "pan": 0.7}

A cropped mezzanine cannot be reframed later — the pixels are gone, and the pan is silently a no-op. Decide at normalize time. Natively-vertical clips have no spare width either, so pan only does something for sources wider than the target aspect.

Still worth checking by hand

  • Whether the shot is any good. Detection answers "is a person here", not "is this the right moment". Run audit_plan.py and look. This is the single biggest source of hand-fixing, so check it every time:

    python3 scripts/audit_plan.py plan/cut_plan.json --out work/plan_audit.png

    One labeled thumbnail per segment, sampled at the midpoint. --at in shows the exact cut frame instead. Fix bad picks in the plan, not after the render.

.mcp.json

tile.json