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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user