B-Roll Video Overlay
Broll overlay

Overlays silent b-roll cutaway clips onto a finished base video — the base audio keeps running underneath while the picture cuts away and returns, and the final duration always equals the base duration. Use when the user asks to add…
---
name: broll-overlay
metadata: {packVersion: 1.3.0}
description: >-
Overlays silent b-roll cutaway clips onto a finished base video — the base
audio keeps running underneath while the picture cuts away and returns, and
the final duration always equals the base duration. Use when the user asks to
add b-roll, add cutaways, or overlay clips on a video: "add b-roll to this",
"cut away to a product shot here", "overlay these clips on my UGC ad". Entered
from an approved base video plus its transcript, which comes from
POST /v1/transcripts and needs no local install; it never generates the base
itself and it never extends a video.
---
# B-roll overlay
Lays silent cutaway clips **over** a finished base video: the base audio runs
untouched underneath, the picture cuts away and returns, and the final durationOverview
Lays silent cutaway clips over a finished base video: the base audio runs untouched underneath, the picture cuts away and returns, and the final duration equals the base duration exactly. That is the whole contract — overlay, never concatenation. A previous run concatenated: 24s for a 15s base, voice dead 12s.
Manual install (npx, ZIP)
npx skills add novoads/agent-skills --skill broll-overlay -g -a claude-codenpx degit novoads/agent-skills/shared/skills/broll-overlay#main ~/.claude/skills/broll-overlayConnects with Agent Alley MCP · agentalley.io/mcp
Files included
SKILL.md9.4 KB
Read SKILL.md here
Show the full text164 lines
B-roll overlay
Lays silent cutaway clips over a finished base video: the base audio runs untouched underneath, the picture cuts away and returns, and the final duration equals the base duration exactly. That is the whole contract — overlay, never concatenation. A previous run concatenated: 24s for a 15s base, voice dead 12s.
Assembly mechanics live entirely in scripts/broll_overlay.py; this file is
judgment only — when to enter, where the windows go, what the user sees first.
Entry condition
Two things must already exist. This skill never generates the base — if there isn't one yet, say so and stop.
A finished, approved base video with exactly one audio stream. Approved means the user has watched it and signed off — b-roll is a polish pass over a locked cut. The script stream-copies one voice track under the picture, so a two-stream base is rejected:
ffmpeg -i base -map 0:v -map 0:a:0 -c copy.Its transcript, with timings. One call to
POST /v1/transcriptswith the base'sjobId(or itsassetIdif it was uploaded). It returnstext, word-levelwords[]andsegments[]— timings in SECONDS, which is what the EDL below takes — plus ansrtyou can ignore here. Price it with the transcript arm ofPOST /v1/estimatesand quote that number, never one from this file. The meter is per minute of source, rounded up from a one-minute minimum, and a repeat of the same source is free, so asking twice costs once. No local install.(The captions endpoint still cannot supply this — it burns subtitles into a new MP4 and returns no text or timings, verified 2026-08-04 and re-checked when transcripts shipped. That finding is correct and is kept here so nobody re-discovers it and files a bug against captions.)
Offline fallback: a local whisper pass (
whisper-clioropenai-whisper) still works and is the path when there is no API key or no network. Note whisper reports timings in milliseconds where the API reports seconds, andwhisper-cliwith no model downloaded returns an EMPTY transcript rather than an error — which reads exactly like a bad render. See README for the model download.Placement is read from the transcript, not eyeballed.
REST key required. A Novoads MCP connector is not a substitute. If
NOVOADS_API_KEYis missing or still the placeholder, stop before any generation work and tell the user: "Before continuing, create an API key at https://novoads.ai/dashboard/settings?tab=api and paste it into.env." That holds even whenmcp__novoads__*tools are connected and authenticated in the session. Never callmcp__novoads__*tools from this repo's workflows: they are a different surface with different behavior, including the units they quote costs in. Repo installs verify with./scripts/check-novoads-env.sh; a solo install checksNOVOADS_API_KEYin the environment.
Pack version. Every /v1 response carries X-Novoads-Pack-Version; mention a newer pack at https://github.com/novoads/agent-skills only when that header names a version NEWER than this file's metadata.packVersion — equal or older is nothing to say, and it is never a reason to stop.
Workflow
- Read the transcript. Note what is said and when — the words drive every decision below.
- Generate the b-roll clips, only after base approval. One clip per moment
you intend to cover, each rendered silent (
audioEnabled: false). Overlay audio is ignored by the script regardless, and generating before approval spends credits on a cut that may still change. Price the set live first —POST /v1/estimatesthis session, the total shown against the user's balance, an explicit yes. Four cutaways is four charges, and a remembered price is not a price. Cast the base actor in every cutaway that shows a person: pass the samereferenceAssetIdsthe base used — Seedance re-casts every render, and a stranger's hands or skin breaks the ad (observed live 2026-08-04: Black base actor, five white-cast cutaways). With no identity ref, keep people out of frame entirely — objects and environments, not "face out of frame". - Propose an EDL. JSON with
base,output, andoverlays(file,start,end,covers).coversis mandatory and quotes the spoken line the window illustrates — a window you can't quote is one you're guessing at. - Show the EDL to the user and wait for approval. Rendering before the user has seen the plan is precisely the failure this step prevents.
- Render:
python3 scripts/broll_overlay.py edl.json - Report the script's verification output verbatim — the measured durations, the audio check and the window count, not a paraphrase. Then offer 2–3 cut variations (different windows, fewer cutaways, a tighter opener) to react to.
Placement judgment
Default cadence, measured 2026-08-04 frame by frame from the reference edit this pack reproduces — ~15.5s, 10–11 shots, a cut every ~1.4s:
- Alternate A-B-A-B: the face returns between every cutaway. Never two overlay windows back to back; the talking head is the spine.
- 4–6 windows per 15s, ~1–1.5s each, ~40–45% coverage. Short and frequent. This retires our older "fewer, longer windows" rule — taste, reversed by measurement; our own two renders ran 2 windows of 2.5s/2.0s, ~30% coverage.
- The b-roll may travel; the base never does. The reference visits 4 distinct b-roll settings across 2–3 rooms; the talking head never moves.
- Never cover the opening hook beat. The first beat is a face making a claim; cutting away there spends the retention the hook just bought.
- End on the person, not on b-roll. The closing beat is the verdict, and it only lands if the viewer is looking at whoever delivers it.
- Put product shots where the product is being spoken about. B-roll that illustrates the current sentence reads as evidence; anything else is filler.
Default shot plan. The reference's five cutaways tell one arc: problem → stress → product macro → dose/usage → relief — 2 product shots to 3 emotional beats. Adapt the imagery to the script, keep the ratio.
Evidence: one measured edit (n=1) — a default, not a law; depart deliberately and
say why (evals.md OV3/OV6). Every run prints its own cadence against this
envelope; --stats prints it without rendering.
Hard rule: assembly always goes through the script
Never hand-write overlay or concat ffmpeg for this task. broll_overlay.py owns
validation, rendering and verification, and it fails loudly on exactly what
fails silently by hand: duration drift, re-encoded audio, overlapping or
zero-frame windows, a clip shorter than its window, a rotated phone base
composited at the wrong geometry, a window that quietly composited nothing.
Improvised ffmpeg is how the founding failure happened.
It needs ffmpeg 7.1 or newer. The script scales each overlay against the base
branch with scale=w=rw:h=rh, and those reference constants arrived in 7.1. On an
older build — Ubuntu 24.04's apt ships 6.1 — the graph does not parse and the
error names rw, not a version. Check with ffmpeg -version before blaming the
EDL; on Debian/Ubuntu the fix is a static build, not apt install ffmpeg.
Escape hatch: if a request genuinely doesn't fit the EDL model — a speed ramp, a picture-in-picture inset, audio that actually needs editing — say so and ask the user how to proceed. Do not improvise around the script.
Other modes
python3 scripts/broll_overlay.py edl.json --dry-run— validate the EDL and print the plan, rendering nothing. Cheap way to check window geometry.python3 scripts/broll_overlay.py edl.json --stats— window count, lengths, coverage, base-return gaps, each marked against the envelope. Never an error.python3 scripts/broll_overlay.py --verify-only FINAL --base BASE [--edl edl.json]— re-check any output against its base, including one from an old session. Pass--edlwhenever you have it: without it the check is duration + audio only, which a plain copy of the base also passes; with it each window's midpoint frame is compared against the base, so a window that composited nothing fails. A run without--edlsays so out loud.
Exit codes: 2 validation, 3 render, 4 verification. A nonzero exit is a
real failure — surface it, don't retry blindly. Renders go to a hidden temp file
beside the output, renamed only after verification passes: a failed run never
leaves a half-written or unverified file at the output path.
Evals
evals.md defines the six scenarios this skill is held to (OV1–OV6).
scripts/test_broll_overlay.py implements the mechanical ones against synthetic
fixtures — run it after touching the script.
| 1 | |
| 2 | name broll-overlay |
| 3 | metadata {packVersion: 1.3.0} |
| 4 | description >- |
| 5 | Overlays silent b-roll cutaway clips onto a finished base video — the base |
| 6 | audio keeps running underneath while the picture cuts away and returns, and |
| 7 | the final duration always equals the base duration. Use when the user asks to |
| 8 | add b-roll, add cutaways, or overlay clips on a video: "add b-roll to this", |
| 9 | "cut away to a product shot here", "overlay these clips on my UGC ad". Entered |
| 10 | from an approved base video plus its transcript, which comes from |
| 11 | POST /v1/transcripts and needs no local install; it never generates the base |
| 12 | itself and it never extends a video. |
| 13 | |
| 14 | |
| 15 | # B-roll overlay |
| 16 | |
| 17 | Lays silent cutaway clips **over** a finished base video: the base audio runs |
| 18 | untouched underneath, the picture cuts away and returns, and the final duration |
| 19 | equals the base duration exactly. That is the whole contract — overlay, never |
| 20 | concatenation. A previous run concatenated: 24s for a 15s base, voice dead 12s. |
| 21 | |
| 22 | Assembly mechanics live entirely in `scripts/broll_overlay.py`; this file is |
| 23 | judgment only — when to enter, where the windows go, what the user sees first. |
| 24 | |
| 25 | ## Entry condition |
| 26 | |
| 27 | Two things must already exist. This skill never generates the base — if there |
| 28 | isn't one yet, say so and stop. |
| 29 | |
| 30 | **A finished, approved base video with exactly one audio stream.** Approved |
| 31 | means the user has watched it and signed off — b-roll is a polish pass over a |
| 32 | locked cut. The script stream-copies one voice track under the picture, so a |
| 33 | two-stream base is rejected: `ffmpeg -i base -map 0:v -map 0:a:0 -c copy`. |
| 34 | **Its transcript, with timings.** One call to **`POST /v1/transcripts`** with |
| 35 | the base's `jobId` (or its `assetId` if it was uploaded). It returns `text`, |
| 36 | word-level `words[]` and `segments[]` — **timings in SECONDS**, which is what |
| 37 | the EDL below takes — plus an `srt` you can ignore here. **Price it with the |
| 38 | transcript arm of `POST /v1/estimates` and quote that number**, never one from |
| 39 | this file. The meter is per minute of source, rounded up from a one-minute |
| 40 | minimum, and **a repeat of the same source is free**, so asking twice costs |
| 41 | once. No local install. |
| 42 | |
| 43 | *(The **captions** endpoint still cannot supply this — it burns subtitles into |
| 44 | a new MP4 and returns no text or timings, verified 2026-08-04 and re-checked |
| 45 | when transcripts shipped. That finding is correct and is kept here so nobody |
| 46 | re-discovers it and files a bug against captions.)* |
| 47 | |
| 48 | **Offline fallback:** a local whisper pass (`whisper-cli` or `openai-whisper`) |
| 49 | still works and is the path when there is no API key or no network. Note |
| 50 | whisper reports timings in **milliseconds** where the API reports seconds, and |
| 51 | `whisper-cli` with no model downloaded returns an EMPTY transcript rather than |
| 52 | an error — which reads exactly like a bad render. See README for the model |
| 53 | download. |
| 54 | |
| 55 | Placement is read from the transcript, not eyeballed. |
| 56 | |
| 57 | > **REST key required. A Novoads MCP connector is not a substitute.** If |
| 58 | > `NOVOADS_API_KEY` is missing or still the placeholder, stop before any |
| 59 | > generation work and tell the user: "Before continuing, create an API key at |
| 60 | > <https://novoads.ai/dashboard/settings?tab=api> and paste it into `.env`." |
| 61 | > That holds even when `mcp__novoads__*` tools are connected and authenticated in |
| 62 | > the session. Never call `mcp__novoads__*` tools from this repo's workflows: they |
| 63 | > are a different surface with different behavior, including the units they quote |
| 64 | > costs in. Repo installs verify with `./scripts/check-novoads-env.sh`; a solo |
| 65 | > install checks `NOVOADS_API_KEY` in the environment. |
| 66 | |
| 67 | **Pack version.** Every `/v1` response carries `X-Novoads-Pack-Version`; mention a newer pack at <https://github.com/novoads/agent-skills> only when that header names a version NEWER than this file's `metadata.packVersion` — equal or older is nothing to say, and it is never a reason to stop. |
| 68 | |
| 69 | ## Workflow |
| 70 | |
| 71 | **Read the transcript.** Note what is said and when — the words drive every |
| 72 | decision below. |
| 73 | **Generate the b-roll clips, only after base approval.** One clip per moment |
| 74 | you intend to cover, each rendered silent (`audioEnabled: false`). Overlay |
| 75 | audio is ignored by the script regardless, and generating before approval |
| 76 | spends credits on a cut that may still change. |
| 77 | **Price the set live first — `POST /v1/estimates` this session, the total |
| 78 | shown against the user's balance, an explicit yes.** Four cutaways is four |
| 79 | charges, and a remembered price is not a price. |
| 80 | **Cast the base actor in every cutaway that shows a person:** pass the same |
| 81 | `referenceAssetIds` the base used — Seedance re-casts every render, and a |
| 82 | stranger's hands or skin breaks the ad (observed live 2026-08-04: Black base |
| 83 | actor, five white-cast cutaways). With no identity ref, keep people out of |
| 84 | frame entirely — objects and environments, not "face out of frame". |
| 85 | **Propose an EDL.** JSON with `base`, `output`, and `overlays` (`file`, |
| 86 | `start`, `end`, `covers`). `covers` is mandatory and **quotes the spoken line |
| 87 | the window illustrates** — a window you can't quote is one you're guessing at. |
| 88 | **Show the EDL to the user and wait for approval.** Rendering before the user |
| 89 | has seen the plan is precisely the failure this step prevents. |
| 90 | **Render:** `python3 scripts/broll_overlay.py edl.json` |
| 91 | **Report the script's verification output verbatim** — the measured durations, |
| 92 | the audio check and the window count, not a paraphrase. Then offer 2–3 cut |
| 93 | variations (different windows, fewer cutaways, a tighter opener) to react to. |
| 94 | |
| 95 | ## Placement judgment |
| 96 | |
| 97 | Default cadence, measured 2026-08-04 frame by frame from the reference edit this |
| 98 | pack reproduces — ~15.5s, 10–11 shots, a cut every ~1.4s: |
| 99 | |
| 100 | **Alternate A-B-A-B: the face returns between every cutaway.** Never two |
| 101 | overlay windows back to back; the talking head is the spine. |
| 102 | **4–6 windows per 15s, ~1–1.5s each, ~40–45% coverage.** Short and frequent. |
| 103 | This retires our older "fewer, longer windows" rule — taste, reversed by |
| 104 | measurement; our own two renders ran 2 windows of 2.5s/2.0s, ~30% coverage. |
| 105 | **The b-roll may travel; the base never does.** The reference visits 4 |
| 106 | distinct b-roll settings across 2–3 rooms; the talking head never moves. |
| 107 | **Never cover the opening hook beat.** The first beat is a face making a |
| 108 | claim; cutting away there spends the retention the hook just bought. |
| 109 | **End on the person, not on b-roll.** The closing beat is the verdict, and it |
| 110 | only lands if the viewer is looking at whoever delivers it. |
| 111 | **Put product shots where the product is being spoken about.** B-roll that |
| 112 | illustrates the current sentence reads as evidence; anything else is filler. |
| 113 | |
| 114 | **Default shot plan.** The reference's five cutaways tell one arc: problem → |
| 115 | stress → product macro → dose/usage → relief — 2 product shots to 3 emotional |
| 116 | beats. Adapt the imagery to the script, keep the ratio. |
| 117 | |
| 118 | Evidence: one measured edit (n=1) — a default, not a law; depart deliberately and |
| 119 | say why (evals.md OV3/OV6). Every run prints its own cadence against this |
| 120 | envelope; `--stats` prints it without rendering. |
| 121 | |
| 122 | ## Hard rule: assembly always goes through the script |
| 123 | |
| 124 | Never hand-write overlay or concat ffmpeg for this task. `broll_overlay.py` owns |
| 125 | validation, rendering and verification, and it fails loudly on exactly what |
| 126 | fails silently by hand: duration drift, re-encoded audio, overlapping or |
| 127 | zero-frame windows, a clip shorter than its window, a rotated phone base |
| 128 | composited at the wrong geometry, a window that quietly composited nothing. |
| 129 | Improvised ffmpeg is how the founding failure happened. |
| 130 | |
| 131 | **It needs ffmpeg 7.1 or newer.** The script scales each overlay against the base |
| 132 | branch with `scale=w=rw:h=rh`, and those reference constants arrived in 7.1. On an |
| 133 | older build — Ubuntu 24.04's apt ships 6.1 — the graph does not parse and the |
| 134 | error names `rw`, not a version. Check with `ffmpeg -version` before blaming the |
| 135 | EDL; on Debian/Ubuntu the fix is a static build, not `apt install ffmpeg`. |
| 136 | |
| 137 | **Escape hatch:** if a request genuinely doesn't fit the EDL model — a speed |
| 138 | ramp, a picture-in-picture inset, audio that actually needs editing — say so and |
| 139 | ask the user how to proceed. Do not improvise around the script. |
| 140 | |
| 141 | ## Other modes |
| 142 | |
| 143 | `python3 scripts/broll_overlay.py edl.json --dry-run` — validate the EDL and |
| 144 | print the plan, rendering nothing. Cheap way to check window geometry. |
| 145 | `python3 scripts/broll_overlay.py edl.json --stats` — window count, lengths, |
| 146 | coverage, base-return gaps, each marked against the envelope. Never an error. |
| 147 | `python3 scripts/broll_overlay.py --verify-only FINAL --base BASE [--edl edl.json]` |
| 148 | — re-check any output against its base, including one from an old session. |
| 149 | **Pass `--edl` whenever you have it:** without it the check is duration + |
| 150 | audio only, which a plain copy of the base also passes; with it each window's |
| 151 | midpoint frame is compared against the base, so a window that composited |
| 152 | nothing fails. A run without `--edl` says so out loud. |
| 153 | |
| 154 | Exit codes: `2` validation, `3` render, `4` verification. A nonzero exit is a |
| 155 | real failure — surface it, don't retry blindly. Renders go to a hidden temp file |
| 156 | beside the output, renamed only after verification passes: a failed run never |
| 157 | leaves a half-written or unverified file at the output path. |
| 158 | |
| 159 | ## Evals |
| 160 | |
| 161 | `evals.md` defines the six scenarios this skill is held to (OV1–OV6). |
| 162 | `scripts/test_broll_overlay.py` implements the mechanical ones against synthetic |
| 163 | fixtures — run it after touching the script. |
| 164 |
Details
- License
- MIT
- Stars on the repo
- ★ 23
- Checked
- Source
- novoads/agent-skills ↗
- Creator
- novoads · 14 listings
Similar skills
See all →
SkillHiggsfield Social Hooks
SkillGab Video Editor
SkillAI Video Clipping Tool
SkillYouTube Shorts Generator
SkillVertical Video Editor
SkillShort Form Video PipelineMore from novoads
SkillVideo Style Template Reverser
SkillVideo Caption Burner
SkillAI Image Ad Creator
SkillVideo Ad Cloner
SkillImage Ad Cloner