docmontage: make music + end-tag mandatory, add Remotion EndTag component

User override during the audit runs made music and an end-tag MANDATORY
defaults for documentary-montage (narration stays optional). This commit
codifies those defaults into the pipeline manifest + director skills and
ships the Remotion end-tag component that the compose stage now
concatenates after the FFmpeg body.

Changes:

- pipeline_defs/documentary-montage.yaml: idea/edit/compose stages now
  require music_plan and end_tag_plan to be present; opt-out requires
  an explicit user note recorded in metadata.

- skills/pipelines/documentary-montage/idea-director.md: rewrote sections
  4 (music MANDATORY), 5 (end-tag MANDATORY with shape
  {text,palette,duration_seconds,render_engine:remotion,component:EndTag}),
  6 (narration OPTIONAL), 7 (updated brief JSON shape), 8 (quality gate).
  Common Pitfalls grew two entries covering silent-contract drift and
  end-tag omission.

- skills/pipelines/documentary-montage/compose-director.md: section 4
  now stops on music-contract violation; new section 4b documents the
  two-engine flow (FFmpeg body -> npx remotion render EndTag -> ffmpeg
  concat). Quality gate checks render_report.metadata.music_mixed and
  end_tag_rendered.

- remotion-composer/src/components/EndTag.tsx (new): bold uppercase
  typographic end-card with animated 0->100% underline draw-in and a
  single left-to-right shimmer sweep. Supports cool_offwhite_on_black
  and warm_ivory_on_black palettes. Renders at 1920x1080 @ 30fps.

- remotion-composer/src/Root.tsx: register EndTag as a first-class
  composition (durationInFrames=165, 5.5s hold) so it can be rendered
  standalone via the Remotion CLI with --props overrides.
This commit is contained in:
calesthio
2026-04-11 00:42:53 -07:00
parent de94d4dba3
commit b80b7a0a61
5 changed files with 351 additions and 18 deletions
@@ -131,8 +131,60 @@ and L-cut sfx layers. Your job is to execute them faithfully:
- L-cut SFX layers = mix at 0.5-0.7 volume, under music.
- No narration unless explicitly present in `edit_decisions.audio.narration`.
**Music is MANDATORY.** If the edit has no music entry, check the brief:
- `brief.metadata.music_plan.source == "none"` with an `opt_out_reason`
the user explicitly opted out. Render silent and note it in
`render_report.warnings`.
- Anything else → STOP. This is a contract violation. Surface it to
the user before rendering. A silent render on a music-mandatory brief
is the loudest failure mode in this pipeline.
Do NOT add ambient noise "to fill the gap".
### 4b. Render The End-Tag Via Remotion, Concatenate After Body
The end-tag is rendered **separately** from the FFmpeg body and
concatenated at the tail. This is deliberate — it sidesteps the
`video_compose.render` scene-adapter mismatch and keeps the two
render engines (FFmpeg for footage, Remotion for typography) cleanly
separated.
Read `brief.metadata.end_tag_plan`:
```json
{
"text": "WE BUILT BOTH WITH THE SAME HANDS.",
"palette": "warm_ivory_on_black",
"duration_seconds": 5.5,
"render_engine": "remotion",
"component": "EndTag"
}
```
Execution path:
1. Compose the body via FFmpeg (cuts + LUT + music + silence window).
Save as `projects/<name>/renders/body.mp4`.
2. Render the end-tag via Remotion CLI with component-specific props:
`npx remotion render EndTag --props='{"text":"...", "palette":"...","durationInFrames":132}' projects/<name>/renders/end_tag.mp4`
(5.5s at 24fps = 132 frames). Canvas must match body canvas.
3. Concat body + end_tag with `ffmpeg -f concat -safe 0 -i list.txt -c copy final.mp4`
or, if encoders don't match, re-encode with the documentary spec.
**End-tag is MANDATORY.** The ONLY way to skip it is an explicit user
opt-out recorded as `end_tag_plan: null` with an `end_tag_opt_out_reason`.
If the brief has an end-tag plan but you skipped rendering it, that is
a contract violation. Stop and surface before finalizing.
Record in `render_report`:
- `end_tag_rendered: true | false`
- `end_tag_path: "projects/<name>/renders/end_tag.mp4"`
- `end_tag_text: "..."` (for audit trail)
If the brief says "no music" and the edit correctly has no music
entry, render silent. Do NOT add ambient noise "to fill the gap".
entry AND `music_plan.source == "none"` with an opt-out reason, render
silent. Do NOT add ambient noise "to fill the gap".
### 5. Render At Documentary Spec
@@ -208,14 +260,16 @@ Record verifications in `render_report.verification_notes`.
### 8. Quality Gate
- Output file exists and plays.
- Duration within ±1s of `brief.duration_seconds`.
- Duration within ±1s of `brief.duration_seconds` (body + end-tag inclusive).
- Resolution matches `target_platform` canvas.
- LUT was applied (or a warning logged).
- Music is present iff the brief planned for it.
- **Music is present** unless `brief.metadata.music_plan.source == "none"` with an explicit opt-out reason.
- **End-tag MP4 was rendered and concatenated** unless `brief.metadata.end_tag_plan` is null with an explicit opt-out reason. Last frame of final MP4 must be the end-tag card in that case.
- First and last frames verified.
- Silence window (if any) verified in the waveform.
- No narration unless brief-approved.
- `render_report.warnings` lists every substitution.
- `render_report.metadata.music_mixed = true` and `render_report.metadata.end_tag_rendered = true` (or explicit opt-out recorded).
## Common Pitfalls
@@ -71,19 +71,60 @@ Shape options:
- **list/catalogue** — "everyone who..." structure, no arc, just
accumulation (good for reverent or elegiac)
### 4. Note Music Intent
### 4. Note Music Intent (MANDATORY)
Documentary montage is inseparable from its music bed. Decide now:
Documentary montage is inseparable from its music bed. **Music is MANDATORY
for this pipeline.** The ONLY way out is an explicit user opt-out (e.g.
"no music, I want it silent") — which MUST be recorded as
`music_plan.source = "none"` with a `music_plan.opt_out_reason` field.
Silent-by-design briefs that feel "pure" at the idea stage regularly look
like abandoned footage at compose time. Do not assume silence will earn
itself. If the user has not mentioned music, ASSUME THEY WANT IT and pick:
- user-provided track (put path in `music_plan.source_path`),
- music library pick (list what's in `music_library/`),
- generated (name the tool and prompt seed),
- or none (silence).
- generated (name the tool and prompt seed with register),
- explicit opt-out (`source: "none"` + `opt_out_reason`).
**Warn the user if no music source is available.** Do not silently
defer this — it becomes an expensive surprise at the asset stage.
### 5. Record The Brief
### 5. Note End-Tag Intent (MANDATORY)
Every documentary-montage film closes on a philosophical end-tag — one
short, abstract line that gives the whole thing meaning. It is rendered
as a Remotion end-card ("shining underlined tag" register — bold weight,
letter-spaced, animated underline) and concatenated after the last clip.
**End-tag is MANDATORY.** The ONLY way out is an explicit user opt-out
recorded as `end_tag_plan: null` with an `end_tag_opt_out_reason` field.
Propose the end-tag at the brief stage. Write 3 options and recommend one.
Expected shape:
```json
{
"end_tag_plan": {
"text": "WE BUILT BOTH WITH THE SAME HANDS.",
"palette": "warm_ivory_on_black",
"duration_seconds": 5.5,
"render_engine": "remotion",
"component": "EndTag"
}
}
```
Keep the copy to 3-9 words. It must be a thesis, not a summary.
### 6. Note Narration Intent (OPTIONAL)
Unlike music and end-tag, narration is OPTIONAL. Absence is fine if
visuals + music + end-tag carry the register. If narration IS used, name
the TTS provider and voice. Record `narration: "none"` explicitly if
there's no narration — don't leave the field missing.
### 7. Record The Brief
Minimum fields the brief must carry:
@@ -97,7 +138,18 @@ Minimum fields the brief must carry:
"sources_allowed": ["pexels", "archive_org", "nasa"],
"generated_clips_allowed": false,
"narration": "none",
"music_plan": { "source": "library", "path": "music_library/dawn_04.mp3" },
"music_plan": {
"source": "generated",
"provider": "elevenlabs",
"prompt_seed": "slow ambient drone in A minor, no percussion, 60s sustained swell, Max Richter register"
},
"end_tag_plan": {
"text": "THE CITY KEEPS ITS OWN VIGIL.",
"palette": "cool_offwhite_on_black",
"duration_seconds": 5.5,
"render_engine": "remotion",
"component": "EndTag"
},
"era_mix": "any",
"target_platform": "social_short"
}
@@ -107,12 +159,15 @@ Minimum fields the brief must carry:
Pexels, "vintage" biases toward Archive.org Prelinger, "any" leaves it
open for the scene director to decide per slot.
### 6. Quality Gate
### 8. Quality Gate
- Thematic question is ONE sentence.
- Tone is ONE register from the fixed list.
- Duration and shape are concrete numbers / enum values.
- Music source is named OR the brief explicitly says "no music".
- `music_plan` is present AND either names a real source OR has
`source: "none"` + `opt_out_reason` (explicit user decision).
- `end_tag_plan` is present AND either has a non-empty `text` OR is
`null` with `end_tag_opt_out_reason` (explicit user decision).
- Sources list is non-empty and at least one requested source is
`available` per `corpus_builder.source_provider_menu` surfaced in
preflight.
@@ -125,3 +180,7 @@ open for the scene director to decide per slot.
- Ignoring duration. A 45s piece with 50 cuts is nausea. A 3-minute
piece with 12 cuts is a slideshow.
- Forgetting to ask about music. The user usually has an opinion.
- Assuming silence will earn itself. It won't. Music is mandatory unless
the user explicitly says no.
- Skipping the end-tag because "the images speak for themselves". They
don't — the end-tag is the thesis. Propose one every time.