| name | render-edl |
| description | deterministically execute an edl.json into a finished master.mp4 — the counterpart to generate-edit-decision-list. Resolves file (conform) and generate-mode clips, applies speed/motion, concatenates as hard cuts, mixes audio bed (music + VO + SFX + silence beats + ducking), burns ASS captions, and writes manifest.json. Use as the edit-video step in the orchestrator and every archetype molecule. |
render-edl
Purpose
Execute an edl.json (Edit Decision List) into a finished master.mp4. The EDL encodes all creator-grammar decisions — clip order, pacing, transitions, audio mix, and captions — produced by generate-edit-decision-list. This atom is the assembly step: it reads the contract and renders it deterministically without any interactive prompts.
Supports both source modes:
- conform (
source.mode = "file") — trims ranges from existing video files.
- generative (
source.mode = "generate") — locates pre-rendered scene clips under <project_dir>/clips/<id>.mp4.
Inputs
edl (required) — path to edl.json. Must conform to the EDL schema (schema version 1.0).
output (required) — path for the output master.mp4.
project_dir (optional) — base directory for resolving relative asset paths. Defaults to meta.project_dir from the EDL; falls back to the directory containing edl.json.
crf (optional, default 18) — x264 quality setting; lower = higher quality.
Workflow
- Load and validate EDL — parse
edl.json, check timeline is non-empty and contiguous (each clip's target_out == next clip's target_in within ±1 frame), confirm exactly one payoff-hold role, and that meta.target_duration is within ±0.5s of the last clip's target_out.
- Resolve and trim clips — for each
timeline[] entry:
source.mode = "file": trim file from in to out with ffmpeg (re-encoded, frame-accurate).
source.mode = "generate": locate <project_dir>/clips/<id>.mp4; if missing, FAIL listing every missing asset.
- Apply speed — if
speed != 1.0, apply setpts=PTS/<speed> (video) + chained atempo (audio).
- Apply motion — support
zoom-punch (stepped crop/scale to the to zoom factor) and push-in (gradual zoompan from from to to). Null motion is a no-op.
- Normalise — scale every clip to
1080x1920 (9:16) or the appropriate resolution for meta.aspect at meta.fps.
- Concatenate — hard-cut concat of all processed clips using ffmpeg
concat filter. Non-hard-cut transition_in values are treated as hard cuts in v1; a note is written to manifest.json.
- Build audio bed — mix
audio.music + audio.vo + per-clip SFX (placed at sfx[].at on the master timeline) + apply each audio_beats[] silence-beat by muting music at at for dur seconds. When audio.ducking = "sidechain-20:1", compress music under VO with ffmpeg sidechaincompress.
- Burn captions — build an ASS subtitle file from all
timeline[].captions[] cues using the preset from caption-style-presets.json. static-sentence holds the full cue; karaoke-pop renders per-word highlights by splitting the cue text into equal-time words. Falls back to grammar.caption_preset when a cue has no per-cue preset.
- Mux and deliver — combine video, audio, and subtitle tracks into
output (master.mp4). Write manifest.json to the same directory, including a recomputed pacing_curve.
CLI:
python3 skills/atoms/assembly/render-edl/scripts/render_edl.py \
--edl <edl.json> --output <master.mp4> [--project-dir <dir>] [--crf 18]
Output
master.mp4 — finished video at meta.fps, normalised resolution, with audio and burned captions.
manifest.json (sibling to master.mp4) — run metadata including:
skill_name, run_id, edl_path, output, created_at, duration_seconds
clips_rendered — per-clip records: id, source_mode, file (resolved path), duration, status
transitions_logged — list of non-hard-cut transition_in values treated as hard cuts
pacing_curve — recomputed mean_shot_len, cuts_per_10s, payoff_hold_ratio
status — pass | fail | needs human review
warnings, errors
Quality Checks
master.mp4 exists and is non-zero; ffprobe reports both video and audio streams.
- Duration of
master.mp4 is within ±0.5s of meta.target_duration.
- Resolution matches the expected value for
meta.aspect (1080×1920 for 9:16).
- All caption cues are present in the ASS file with correct timing.
manifest.json exists with status: pass and all clips_rendered paths resolve on disk.
pacing_curve.payoff_hold_ratio is between 2.0 and 4.0 (warn if outside range, don't fail).
Failure Modes
- EDL validation failure — non-contiguous timeline, missing payoff-hold, or
target_duration mismatch; script exits with a clear message listing every violation.
- Missing generate-mode clips — script fails immediately listing every
<project_dir>/clips/<id>.mp4 that does not exist. Re-run after generating the missing clips.
- Missing conform-mode source file — script exits with a clear error naming the file.
ffmpeg not installed — install via brew install ffmpeg or apt install ffmpeg.
- No audio streams — if
audio.music, audio.vo, and all SFX are null/absent, the master is produced with a silent audio track; a warning is written to the manifest.
- Caption preset not found — if the cue's preset does not exist in
caption-style-presets.json, the fallback preset platform-default is used and a warning is written.
- Speed out of range —
speed values < 0.1 or > 8.0 are rejected with a clear error.