Purpose
Ingest a video file and measure its editorial DNA: every cut point, the pacing curve (cuts per 10-second window), the audio mode (music-only / vo+music / speech-only), an optional word-level transcript, and the beat grid when music is present. The final output is a grammar-profile.json that characterises the video against the eight creator-grammar archetypes (see CREATOR_GRAMMAR.md §8 and generate-edit-decision-list/references/archetypes.json) and identifies the best-fit archetype with a confidence score.
Two primary use cases:
- Reference teardown — drop a winning competitor or reference video in; get back a structured profile you can hand to
generate-edit-decision-listto replicate its pacing. - Conform-mode input — re-edit an existing video to this grammar by feeding
grammar-profile.jsonintogenerate-edit-decision-list(conform source mode).
Inputs
| Field | Required | Notes |
|---|---|---|
source |
yes | Path to a video file (mp4, mov, webm, m4v). |
output_dir |
yes | Directory where all outputs are written. Created if absent. |
--no-transcribe |
no | Skip Whisper transcription (default: transcription is on). Pass this flag for speed when transcript is not needed. |
CLI:
python3 skills/atoms/planning/analyze-reference-grammar/scripts/analyze_grammar.py \
--source <video> --output-dir <dir> [--no-transcribe]
Workflow
- Probe — run
ffprobeto extract duration, resolution (width × height), fps, and aspect ratio. - Cut detection — run ffmpeg scene-detect (
select='gt(scene,0.3)',showinfo) on the video and parse the frame-number / timestamp output to build a list of cut timestamps. Derive:cut_count,mean_shot_len,shortest_shot,longest_shot,cuts_per_10s[](one integer per 10-second window across the full duration), andpayoff_hold— the single longest shot and its ratio to the mean (the payoff-hold is the "money shot" perCREATOR_GRAMMAR.md§2). - Audio mode — determine whether speech and/or music are present:
- Run Whisper (
whisperCLI oropenai-whisperPython API) on the audio stream; if it returns words →has_speech = true. - Run
ffmpeg volumedetectto measure mean loudness; also scan for silence intervals withsilencedetect. If the video has audio but Whisper yields no words (or very few words) → infer music/ambient track →has_music = true. - Classify
audio_modeasmusic-only|vo+music|speech-onlyusing the table inCREATOR_GRAMMAR.md§6.
- Run Whisper (
- Beat grid (best-effort) — if
has_musicis true, shell out to../extract-beat-grid/scripts/extract_beats.py --source <video> --output-dir <output_dir>. Read backbeat-grid.jsonto extractbpm. Tolerate failure (non-zero exit, missing file) — on failure setbpm: nulland log a warning. - Transcript — if
--no-transcribeis not set and Whisper succeeded, writewords.json(array of{word, start, end}objects from Whisper's word-level output). - Archetype classification — score each of the 8 archetypes in
archetypes.jsonagainst the measured profile:- Match
audio_mode(exact match scores 40 points). - Compare mean
cuts_per_10sto the archetype'scuts_per_10s.bodytarget (closer = higher score, max 30 points). - Payoff-hold presence vs. archetype's
payoff_hold.required(match = 20 points). - Aspect ratio match (9:16 is native; 16:9 or 1:1 = slight penalty, max 10 points).
- Pick the highest-scoring archetype as
archetype_match.id; normalise scores to a 0–1 confidence. Record the second-highest asrunner_up.
- Match
- Write outputs —
grammar-profile.json, optionalwords.json, andmanifest.json.
grammar-profile.json schema
{
"schema_version": "1.0",
"source": "<absolute path>",
"duration_s": 28.4,
"resolution": "1080x1920",
"fps": 30.0,
"aspect": "9:16",
"cuts": [0.0, 1.33, 2.67, 5.10, 8.45, 12.00, 15.50, 19.20, 22.80, 26.30],
"cut_count": 9,
"mean_shot_len": 2.84,
"shortest_shot": 0.5,
"longest_shot": 5.2,
"payoff_hold": {
"detected": true,
"start_s": 8.45,
"end_s": 13.65,
"ratio": 1.83
},
"cuts_per_10s": [4, 3, 2],
"audio_mode": "vo+music",
"has_music": true,
"has_speech": true,
"bpm": 128.0,
"archetype_match": {
"id": "vo-product-demo",
"confidence": 0.82,
"runner_up": "brand-process-film"
},
"words_file": "words.json"
}
Output
<output_dir>/grammar-profile.json— the complete grammar measurement (schema above).<output_dir>/words.json— word-level transcript array (omitted if--no-transcribe).<output_dir>/manifest.json— skill run metadata (standard manifest schema).
Quality Checks
grammar-profile.jsonis valid JSON and contains all required top-level keys.cut_countequalslen(cuts) - 1(the first entry is always 0.0 for the video start; each subsequent entry is a detected scene change).mean_shot_lenequalsduration_s / cut_countwithin ±0.1s (orduration_sif no cuts detected).payoff_hold.ratiois 2–4× the mean shot length whendetected: true(perCREATOR_GRAMMAR.md§2).audio_modeis one of the three valid string values.archetype_match.idis one of the 8 IDs inarchetypes.json.archetype_match.confidenceis in [0, 1].manifest.jsonstatusispasswhen no fatal errors occurred.
Failure Modes
- Source not found — the script exits with a clear error before doing any work.
- ffmpeg / ffprobe not on PATH — install via
brew install ffmpeg. - No scene changes detected (single-shot video or very low contrast) —
cut_countis 0 andcutsis[0.0]; archetype classification still runs againstcuts_per_10s = [0, ...]. - Whisper not installed — transcription is silently skipped,
has_speechfalls back to energy-only heuristic; a warning is added to the manifest. - Beat-grid extraction failure —
bpmis set tonull, warning logged; the rest of the profile is still written. - Video has no audio stream —
audio_modedefaults tomusic-only(safest assumption);has_musicandhas_speechare bothfalse;bpmisnull. - Very short video (< 2s) —
cuts_per_10smay be empty or have a single element; archetype classification scores may be low-confidence (< 0.4).