Purpose
This is the editorial brain of the creator-grammar pipeline. It transforms:
- a chosen archetype (from
references/archetypes.json) - a script or transcript with word-timestamps
- an optional storyboard scene table or shot-list JSON
- an optional beat-grid JSON
into a concrete, validated edl.json — the Edit Decision List defined in
references/edl-schema.md. The EDL encodes every cut point, role, transition,
caption cue, audio beat, and pacing-curve metric for the video. Downstream
steps (render-edl, add-zoom-cuts, synthesize-sfx-ffmpeg, add-captions-burn)
consume it without further planning decisions.
Two source modes:
- generative — timeline clips are scenes still to be created. The EDL records a generation spec per clip that the orchestrator passes to image/video gen atoms.
- conform — timeline clips are time-ranges of existing video files. The EDL
records
source.file,source.in,source.outper clip. Used to recut an existing video to creator grammar.
Both modes produce the same schema; render-edl executes either.
Inputs
| Input | Required | Notes |
|---|---|---|
archetype |
yes | one of the 8 IDs in references/archetypes.json |
script_source |
one of the two | path to a script-locked.md markdown file + word_timestamps_path JSON, OR |
words_json |
one of the two | path to a [{text, start, end}] word-timestamps JSON (from transcribe-audio or similar) |
scene_table |
no | path to a storyboard scene table JSON or shot-list JSON ([{scene_id, description, duration_s, ...}]). If omitted, clips are derived from VO segments. |
beat_grid |
no | path to a beat-grid.json (from extract-beat-grid). Required only if archetype has beat_sync: true. |
target_duration |
yes | target video runtime in seconds |
source_mode |
yes | "generative" or "conform" |
source_files |
conform mode only | list of source video file paths the conform clips are drawn from |
output_path |
yes | where to write edl.json |
Workflow
This is an LLM-executed planning workflow. Follow these steps in order.
Step 1 — Load archetype parameters
Read references/archetypes.json and extract the record for archetype. Note
audio_mode, caption_family, caption_preset, cuts_per_10s, payoff_hold,
transition_default, and beat_sync.
Step 2 — Compute the pacing curve
Run scripts/edl_pacing.py to derive the segment boundaries and target cut counts:
python3 skills/atoms/planning/generate-edit-decision-list/scripts/edl_pacing.py \
--archetype <archetype> \
--target-duration <target_duration>
Record hook_end_s, payoff_start_s, payoff_end_s, tail_start_s,
target_cuts, mean_shot_len_s, and payoff_hold_dur_s — you will use these
in every subsequent step.
Step 3 — Lay out the timeline clips
Build the ordered timeline[] array honoring the pacing targets:
Generative mode:
- Use the scene table as the primary source of clips (one clip per scene or sub-scene). If no scene table is provided, derive clips from VO phrase boundaries in the word-timestamps.
- Assign
roleto each clip:- The first clip is always
"hook". - Clips in the hook segment (before
hook_end_s) use"hook". - Clips covering feature narration use
"feature". - B-roll cuts away from main VO use
"b-roll". - Mark exactly one clip
"payoff-hold"(see Step 4). - The final clip(s) after
tail_start_suse"feature"or"establishing".
- The first clip is always
- Set
transition_inper clip using the archetype'stransition_defaultas the base, then upgrade specific boundaries per the creator grammar:silence-masked-cuton the biggest edit (ideally the cut into the payoff).jump-cuton breath/pause removal (gaps > 0.35s in the word-timestamps).beat-synced-cutifbeat_sync: trueand a beat-grid is provided.b-roll-insertwhen cutting away from VO.
- Fill in
source.spec.descriptionper clip from the scene table or from the VO phrase that clip covers. Write in camera-language format:"<shot-size>, <subject>, <action>"(e.g."ECU, spray nozzle, liquid emerging"). - Set
shot.sizeandshot.movementper clip following the WS→MS→CU/ECU cycling pattern fromCREATOR_GRAMMAR.md §5. - Set
target_inandtarget_outso clips are contiguous (no gaps, no overlaps) and the final clip'starget_outis within ±0.5s oftarget_duration.
Conform mode:
- Load each source file's word-timestamps to map speech to time.
- Identify the strongest claim (the most direct, benefit-forward sentence) in the
transcript and move it to position 0.0–
hook_end_sas the hook clip, even if it appeared later in the original recording. - Remove inter-word and inter-sentence gaps > 0.35s by splitting at those
boundaries: each contiguous speech run becomes a clip with
transition_in: "jump-cut". - Record
source.mode: "file",source.file,source.in,source.outfor each clip. - Keep the same role,
target_in/target_out, and pacing rules as generative mode.
Step 4 — Mark the payoff-hold clip
Identify the clip that represents the hero moment: the product working, the key
reveal, the strongest visual proof of the claim. This clip should fall in the
payoff window (payoff_start_s to payoff_end_s).
- Set its
roleto"payoff-hold". - Set its duration to approximately
payoff_hold_dur_s(from Step 2), which ismean_shot_len_s × payoff_hold.multiplierfrom the archetype. Acceptable range is 2.0×–4.0× the mean shot length. - Record its
idingrammar.payoff_hold_id. - Set
transition_in: "silence-masked-cut"on this clip (the biggest cut is hidden in the music gap).
Step 5 — Place caption cues
Use the archetype's caption_family to decide caption style:
karaoke-pop family (speech-only or vo+music archetypes):
- Generate one caption cue per word or word-group from the word-timestamps.
- Each cue:
{ "text": "<word(s)>", "start": <master_s>, "end": <master_s>, "preset": "<caption_preset>" }. - For conform mode, map source-file word timestamps to master-timeline positions.
- Suppress captions on clips that have their own on-screen text (e.g. hyperframes,
end cards, title cards). Add a
notescomment on those clips.
static-title-card family (music-only archetypes):
- Place one caption cue on the hook clip (or a dedicated title-card clip) that holds for the clip's full duration. This is the single on-screen claim.
- Do not place per-word cues.
Set grammar.caption_family and grammar.caption_preset from the archetype.
Step 6 — Snap cut points to beat grid (when applicable)
If beat_sync: true for the archetype AND a beat-grid file is provided:
- For each clip boundary in the body and tail segments, find the nearest bar boundary in the beat-grid.
- Snap
target_into that bar boundary if within ±150ms (a deliberate half-beat tolerance — do not force snapping that would break a narration phrase). - Set
transition_in: "beat-synced-cut"on those clips. - The hook clip's first cut should land on a downbeat.
Step 7 — Place silence beats and SFX
Silence beats (audio_beats[]):
- Place one silence beat immediately before the payoff-hold clip (the
attime ispayoff_hold.target_in - 0.10s, duration 0.25–0.6s from the archetype's characteristic gap). - For
broll-narrative-essay: place one additional silence beat at the structural pivot between acts (~50% of the video).
SFX on cuts (clip.sfx[]):
- Add
{ "type": "whoosh", "at": <target_in> }on feature-flash cuts in the hook and body segments. - Add
{ "type": "tap", "at": <target_in> }on match-cuts or zoom-punch cuts. - Do not add SFX to the payoff-hold clip or tail clips.
Step 8 — Assemble the EDL object
Write the complete edl.json per references/edl-schema.md:
meta— archetype, target_duration, aspect, fps, audio_mode, source_mode, project_dirgrammar— hook_type, caption_family, caption_preset, pacing_profile, payoff_hold_id, loopaudio— music, beat_grid, vo, ducking, music_drop_s (set topayoff_hold.target_in)timeline— all clips from Step 3 + Step 4audio_beats— from Step 7pacing_curve— fill from pacing script output and actual clip durations:cuts_per_10s: array of cut density per 10s windowmean_shot_len: total_duration / number_of_clipspayoff_hold_ratio: payoff_hold_duration / mean_shot_lensegments: hook_end_s, payoff_start_s, tail_start_s
Step 9 — Validate
Run the validator before reporting done:
python3 skills/atoms/planning/generate-edit-decision-list/scripts/edl_validate.py <output_path>
If the validator reports errors, fix each one and re-run. Do not deliver an
invalid EDL. All nine rules in references/edl-schema.md §"Validation rules"
must pass.
Output
edl.jsonatoutput_path— validated, conforming toreferences/edl-schema.md.- Optionally a brief
planning-notes.mdin the same folder with the key editorial decisions (archetype rationale, payoff clip choice, beat-sync adjustments).
The EDL schema is fully documented in references/edl-schema.md. The pacing_curve
section is derived from the pacing script and actual clip durations.
Quality Checks
scripts/edl_validate.pyexits 0 (all nine rules pass).- Exactly one clip has
role: "payoff-hold"and its duration is 2–4×mean_shot_len. - All clips are contiguous:
timeline[N].target_out == timeline[N+1].target_inwithin ±1 frame (33ms at 30fps). - Last clip
target_outis within ±0.5s ofmeta.target_duration. - For conform mode: every
source.filepath exists on disk. - For beat-synced archetypes with a beat-grid: every
beat-synced-cutclip'starget_inis within ±50ms of a grid boundary. - No karaoke-pop captions in a music-only EDL.
grammar.caption_presetis a known preset incaption-style-presets.json.meta.archetypeis one of the 8 IDs inarchetypes.json.
Failure Modes
- No word-timestamps provided for a karaoke-pop archetype — cannot place
per-word caption cues. Request the word-timestamps JSON from
transcribe-audioor similar before running. - Scene table missing for generative mode — fall back to VO phrase boundaries
to derive clips, but flag in
planning-notes.mdthat visual descriptions are placeholder-level and need review before generation. - No strongest claim identifiable in conform mode — log a warning in
planning-notes.mdand keep the original order; do not silently reorder. - Beat-grid missing for a
beat_sync: truearchetype — proceed without beat snapping and add a warning. Recordaudio.beat_grid: null. - Payoff window is too short for the archetype's payoff multiplier — log the
shortfall in
planning-notes.mdand use the longest available shot in the window. - Validator exits non-zero — do not deliver the EDL. Fix each reported error
and re-validate. Common fixes: reorder clips by
target_in; adjust the last clip'starget_outto matchmeta.target_duration; confirm exactly one payoff-hold clip.