broll-core (shared library)
Single source of truth for the broll-* family so the base-layer skills produce
interchangeable output and the avatar-PiP compositor lives in exactly one
place (DRY). It is a library, not a user-facing skill — you never invoke it
directly.
What's here (scripts/)
| Module | What |
|---|---|
_common.py |
Canonical ASPECTS (9:16 = 1080×1920) + DEFAULT_FPS=30, run/ffprobe_* wrappers, next_index + append_manifest (numbered clips + manifest.json), find_font. Set C.PREFIX per skill for nicer logs. |
pip_overlay.py |
overlay_pip(base, avatar, ..., length=) — composites the talking avatar in a PiP circle (default) or split layout over any base clip. length="avatar" (default): avatar audio drives length, base loops. length="base": the base drives length (e.g. a recorded demo), avatar freezes at its narration end. Used by every base-layer skill's --avatar shortcut, by broll-browser-recorder (base-driven) and by broll-demo-avatar. |
Who uses it
broll-web-capture ──────┐
broll-browser-recorder ─┤
broll-terminal ─────────┼─► broll-core (geometry, ffmpeg, manifest, PiP compositor)
broll-demo-avatar ──────┘
How skills import it
Each consumer adds broll-core/scripts to sys.path (resolved relative to the
skill, assuming the sibling .cursor/skills/<skill>/scripts/ layout) and then
import _common as C / import pip_overlay as PIP. If broll-core is missing,
the consumer fails fast with an install hint.
Setup
pip3 install -r .cursor/skills/broll-core/scripts/requirements.txt
ffmpeg + ffprobe must be on PATH. This library has no other runtime deps.
Conventions (keep these stable — other skills depend on them)
- Geometry: outputs use
ASPECTS; 9:16 is 1080×1920 at 30fps, matchingavatar-reel-composer. - Output: clips are
NNN_<slug>.mp4(zero-padded, auto-incremented vianext_index) and each run appends an entry tomanifest.json({"clips": [...]}), a drop-in foravatar-reel-composer(broll_source: "existing"). - Avatar PiP: by default (
length="avatar") the avatar carries audio and drives length; the base loops to cover it. Withlength="base"the base drives length (a recorded demo is the hero) and the avatar freezes on its last frame once its narration ends. Either way the avatar clip should be a static, face-forwardpipshot (avatar-camera-angles --move pip) lip-synced locked withp-video-avatar.