Adom Video Post-Production
Public Made by Adomby adom
AI-driven studio for finishing demo videos: review every captured clip, flag and re-record the weak ones, speed up dead air, narrate, mux and auto-level, validate the final cut, and publish straight to the wiki, driven end to end by your AI.
name: gang-takes description: > Cut several screen-recording takes to their action and gang them into one narrated video: real time by default, the idle seconds before, between and after the action removed by change detection, the real duration of the work stated on screen, narration written and fitted to the measured length of the cut, text overlays in the Adom brand fonts on a soft glow, no cards, no letterbox. Use for "combine these clips into one video", "gang up the recordings", "only show the action", "cut the boring parts", "narrate to the length of the video", "captions over the screen recording", "one video from the three takes", "brand fonts on video captions", "captions file for the wiki video", "chapters for the video", "adom-video widget".
gang-takes: one narrated video from several takes, action only
gang_takes.py (next to this file) turns a manifest of takes into one MP4. It made the
three-engine routing video on the kicad-bridge page: three raw takes of 12, 38 and 43 seconds
became 21 seconds of real-time action.
The rule that matters: show action, never delay it
A viewer wants to see the thing happen. So:
- Real time by default. If the work on screen is already fast, it is not sped up.
speedis per segment and off unless you set it. - Cut the idle, do not compress it. The tool scans the frames of a region you name for the moments the screen changes, keeps a short beat before and after each change (default 0.4 s before, 0.8 s after, 1.6 s after the last one) and drops everything else: the seconds before the first commit, the waits between commits, the tail after the last.
- Say how long it really took. Put the real duration in the
chiptext ("real time 3.2 s") and in the narration. If idle was cut out, say so ("real time 25 s, idle cut"). - Measure the cut first, then write the words. Run with
--measureto get the length of every segment, write narration for those numbers, then run for real. The tool synthesises with adom-tts, trims the leading and trailing silence, and raises the speaking rate in steps up to +20% until the audio fits the segment. If it still does not fit it tells you the overrun instead of padding the video with a hold. - Pure screen video. No title card, no outro, no letterbox. The only thing added to the frames is text.
The closing chart
Every comparison video ends with a bar chart of the measured numbers (real time per engine, DRC errors, unconnected items, whatever the video compared). Give the manifest a chart block: title, up to three subtitle lines, rows of {name, value, unit, note}, optional max for the bar scale, footer (the page URL), and narration that reads the numbers out. It is rendered in the brand fonts on the dark background, fades in, and lasts as long as its narration. This is the one frame that is not a screen recording; it carries data, not decoration.
Text on the frame
Segment label in Familjen Grotesk 700 (teal, top left), chip in JetBrains Mono 500 (top
right), optional captions in Satoshi 500 (bottom left, wrapped). All on a blurred dark halo so
they read over busy UI. Fonts are fetched once from wiki.adom.inc/static/fonts into
~/.cache/adom-brand-fonts; colours are the wiki's tokens (accent #00b8b0, dark #0d1117,
text #e6edf3).
Manifest
{
"size": "1920x1080",
"fps": 30,
"region": [0.26, 0.16, 0.77, 0.86],
"segments": [
{
"clip": "/path/take1.mp4",
"label": "1 / 3 Freerouting",
"chip": "real time 3.2 s",
"narration": "Freerouting: one call, six nets, three point two seconds.",
"pre": 0.4, "post": 0.8, "last_post": 1.6
},
{
"clip": "/path/take2.mp4",
"label": "2 / 3 Claude Fable 5.1",
"chip": "real time 25 s, idle cut",
"narration": "Claude Fable 5.1, net by net: one undo step per call, DRC before each lands. Six nets in twenty-five seconds.",
"last_post": 2.0
}
]
}
- A segment may carry its own
regionwhen its clip has another layout (another box, or a caption band that changes every second, which would otherwise keep every frame). Set it per segment and the manifest'sregionis the default for the rest. regionis the part of the frame to watch for change, as fractions (x0, y0, x1, y1). Name the canvas, not the whole screen, so a clock or a status bar does not count as action.eventslets you pass the change moments yourself (seconds into the raw take) when detection picks up the wrong thing;min_change_pxtunes sensitivity (pixels changed at 640 px width).narration_mp3reuses audio you already made. Write narration text the way it should be spoken: "kicad route net", "net three", "three point two seconds".captionsare optional and spread evenly over the segment.
Run
pip install pillow numpy fonttools brotli # once
python3 ~/.claude/skills/gang-takes/gang_takes.py --manifest takes.json --out final.mp4 --measure
# read the lengths, write the narration to them, then
python3 ~/.claude/skills/gang-takes/gang_takes.py --manifest takes.json --out final.mp4
Output: final.mp4 (H.264 yuv420p, AAC, faststart), final-poster.jpg, and a report with the
detected events, the kept intervals, each segment's length and how the narration was fitted.
It also writes, for free, from what it already knows:
final.vtt: captions from the narration text. The narration is adom-tts reading your text, so the words are exact and every cue starts where its segment's narration starts and ends where the trimmed clip ends; long lines split at sentences, then commas, at most two lines of about 42 characters, each timed by its share of the characters. Segments with onlynarration_mp3and nonarrationtext get no cues.final.chapters.json: one chapter per segment, titled fromlabelwith the "1 / 3" counter dropped (setchapteron a segment, or on thechart, to name it yourself; the chart defaults to its title).
The report's embed line is the wiki tag that uses all four files. Push them next to each other on the
page and fill in OWNER/SLUG/PATH:
::adom-video{video="/OWNER/SLUG/render/PATH/final.mp4" poster="/OWNER/SLUG/render/PATH/final-poster.jpg" captions="/OWNER/SLUG/render/PATH/final.vtt" chapters="/OWNER/SLUG/render/PATH/final.chapters.json"}
::adom-video (adom/widgets) is the wiki's YouTube-style player: every YouTube shortcut, a timeline you
can hit, chapters as bar segments, C toggles the captions. Captions and chapter files are fetched by the
reader's browser without a login, so on a PRIVATE page pass a JSON manifest instead, which the wiki
inlines: ::adom-video{src="/OWNER/SLUG/render/PATH/final.video.json"} with
{"video": "...mp4", "poster": "...", "chapters": [..the chapters.json array..], "captions": "WEBVTT\n\n..the vtt text.."}.
Give the absolute path: --out /abs/path/final.mp4 (a relative --out breaks the concat step).
Check it before you ship it
Extract frames at the start of each segment, just after a change, and the last second, and look at them. Look for: the take is visible, the label does not sit on the thing being demonstrated, every change you expected made it into the cut (compare the report's events with the number of commits you made), and the narration report says it fit.
Recording without disturbing the user, and when the foreground is worth it
On a user's box, never bring the recorded window to the foreground and never move the mouse for
a video. Record the window (desktop_record_window_start {hwnd}), not the monitor, and leave it
behind whatever the user is doing. OpenGL canvases (KiCad's default) do not paint while covered
and come out white in a window capture; for KiCad, set graphics.canvas_type to 2 in
kicad_common.json while KiCad is closed (keep a copy and restore it after), which is KiCad's
fallback canvas and paints in the background. Verified 2026-09-12 on arav-rog: the editor covered
by the user's Hydrogen window, window capture shows the board and every change. Drive the app
over IPC, menu commands and UI Automation; frames captured with kicad_screenshot_all are
background too.
On a box that is yours (a dedicated test or demo machine), the OpenGL canvas in the foreground
gives the better picture: open with foreground:true and a reason, maximize, record the
monitor. Use the foreground only when it benefits the recording and nobody is working on that
screen; the background path is the default.
Publishing
Push the MP4 and poster to the page and embed with a <video> tag whose src is
/blob/app/<page>/docs/videos/<file>.mp4, with a plain "Watch" link to the
/api/pages/<owner>/<page>/files/... URL for phones. Confirm the URL serves with curl before
telling anyone it is live.
---
name: gang-takes
description: >
Cut several screen-recording takes to their action and gang them into one
narrated video: real time by default, the idle seconds before, between and
after the action removed by change detection, the real duration of the work
stated on screen, narration written and fitted to the measured length of the
cut, text overlays in the Adom brand fonts on a soft glow, no cards, no
letterbox. Use for "combine these clips into one video", "gang up the
recordings", "only show the action", "cut the boring parts", "narrate to the
length of the video", "captions over the screen recording", "one video from
the three takes", "brand fonts on video captions", "captions file for the wiki video",
"chapters for the video", "adom-video widget".
---
# gang-takes: one narrated video from several takes, action only
`gang_takes.py` (next to this file) turns a manifest of takes into one MP4. It made the
three-engine routing video on the kicad-bridge page: three raw takes of 12, 38 and 43 seconds
became 21 seconds of real-time action.
## The rule that matters: show action, never delay it
A viewer wants to see the thing happen. So:
- **Real time by default.** If the work on screen is already fast, it is not sped up. `speed`
is per segment and off unless you set it.
- **Cut the idle, do not compress it.** The tool scans the frames of a region you name for the
moments the screen changes, keeps a short beat before and after each change (default 0.4 s
before, 0.8 s after, 1.6 s after the last one) and drops everything else: the seconds before
the first commit, the waits between commits, the tail after the last.
- **Say how long it really took.** Put the real duration in the `chip` text ("real time 3.2 s")
and in the narration. If idle was cut out, say so ("real time 25 s, idle cut").
- **Measure the cut first, then write the words.** Run with `--measure` to get the length of
every segment, write narration for those numbers, then run for real. The tool synthesises
with adom-tts, trims the leading and trailing silence, and raises the speaking rate in steps
up to +20% until the audio fits the segment. If it still does not fit it tells you the
overrun instead of padding the video with a hold.
- **Pure screen video.** No title card, no outro, no letterbox. The only thing added to the
frames is text.
## The closing chart
Every comparison video ends with a bar chart of the measured numbers (real time per engine, DRC errors, unconnected items, whatever the video compared). Give the manifest a `chart` block: `title`, up to three `subtitle` lines, `rows` of `{name, value, unit, note}`, optional `max` for the bar scale, `footer` (the page URL), and `narration` that reads the numbers out. It is rendered in the brand fonts on the dark background, fades in, and lasts as long as its narration. This is the one frame that is not a screen recording; it carries data, not decoration.
## Text on the frame
Segment label in Familjen Grotesk 700 (teal, top left), chip in JetBrains Mono 500 (top
right), optional captions in Satoshi 500 (bottom left, wrapped). All on a blurred dark halo so
they read over busy UI. Fonts are fetched once from `wiki.adom.inc/static/fonts` into
`~/.cache/adom-brand-fonts`; colours are the wiki's tokens (accent #00b8b0, dark #0d1117,
text #e6edf3).
## Manifest
```json
{
"size": "1920x1080",
"fps": 30,
"region": [0.26, 0.16, 0.77, 0.86],
"segments": [
{
"clip": "/path/take1.mp4",
"label": "1 / 3 Freerouting",
"chip": "real time 3.2 s",
"narration": "Freerouting: one call, six nets, three point two seconds.",
"pre": 0.4, "post": 0.8, "last_post": 1.6
},
{
"clip": "/path/take2.mp4",
"label": "2 / 3 Claude Fable 5.1",
"chip": "real time 25 s, idle cut",
"narration": "Claude Fable 5.1, net by net: one undo step per call, DRC before each lands. Six nets in twenty-five seconds.",
"last_post": 2.0
}
]
}
```
- A segment may carry its own `region` when its clip has another layout (another box, or a caption band that changes every second, which would otherwise keep every frame). Set it per segment and the manifest's `region` is the default for the rest.
- `region` is the part of the frame to watch for change, as fractions (x0, y0, x1, y1). Name
the canvas, not the whole screen, so a clock or a status bar does not count as action.
- `events` lets you pass the change moments yourself (seconds into the raw take) when detection
picks up the wrong thing; `min_change_px` tunes sensitivity (pixels changed at 640 px width).
- `narration_mp3` reuses audio you already made. Write narration text the way it should be
spoken: "kicad route net", "net three", "three point two seconds".
- `captions` are optional and spread evenly over the segment.
## Run
```bash
pip install pillow numpy fonttools brotli # once
python3 ~/.claude/skills/gang-takes/gang_takes.py --manifest takes.json --out final.mp4 --measure
# read the lengths, write the narration to them, then
python3 ~/.claude/skills/gang-takes/gang_takes.py --manifest takes.json --out final.mp4
```
Output: `final.mp4` (H.264 yuv420p, AAC, faststart), `final-poster.jpg`, and a report with the
detected events, the kept intervals, each segment's length and how the narration was fitted.
It also writes, for free, from what it already knows:
- **`final.vtt`**: captions from the narration text. The narration is adom-tts reading your text, so the
words are exact and every cue starts where its segment's narration starts and ends where the trimmed
clip ends; long lines split at sentences, then commas, at most two lines of about 42 characters, each
timed by its share of the characters. Segments with only `narration_mp3` and no `narration` text get no cues.
- **`final.chapters.json`**: one chapter per segment, titled from `label` with the "1 / 3" counter
dropped (set `chapter` on a segment, or on the `chart`, to name it yourself; the chart defaults to its title).
The report's `embed` line is the wiki tag that uses all four files. Push them next to each other on the
page and fill in OWNER/SLUG/PATH:
```markdown
::adom-video{video="/OWNER/SLUG/render/PATH/final.mp4" poster="/OWNER/SLUG/render/PATH/final-poster.jpg" captions="/OWNER/SLUG/render/PATH/final.vtt" chapters="/OWNER/SLUG/render/PATH/final.chapters.json"}
```
`::adom-video` (adom/widgets) is the wiki's YouTube-style player: every YouTube shortcut, a timeline you
can hit, chapters as bar segments, C toggles the captions. Captions and chapter files are fetched by the
reader's browser without a login, so on a PRIVATE page pass a JSON manifest instead, which the wiki
inlines: `::adom-video{src="/OWNER/SLUG/render/PATH/final.video.json"}` with
`{"video": "...mp4", "poster": "...", "chapters": [..the chapters.json array..], "captions": "WEBVTT\n\n..the vtt text.."}`.
Give the absolute path: `--out /abs/path/final.mp4` (a relative `--out` breaks the concat step).
## Check it before you ship it
Extract frames at the start of each segment, just after a change, and the last second, and
look at them. Look for: the take is visible, the label does not sit on the thing being
demonstrated, every change you expected made it into the cut (compare the report's events with
the number of commits you made), and the narration report says it fit.
## Recording without disturbing the user, and when the foreground is worth it
On a user's box, never bring the recorded window to the foreground and never move the mouse for
a video. Record the window (`desktop_record_window_start {hwnd}`), not the monitor, and leave it
behind whatever the user is doing. OpenGL canvases (KiCad's default) do not paint while covered
and come out white in a window capture; for KiCad, set `graphics.canvas_type` to 2 in
`kicad_common.json` while KiCad is closed (keep a copy and restore it after), which is KiCad's
fallback canvas and paints in the background. Verified 2026-09-12 on arav-rog: the editor covered
by the user's Hydrogen window, window capture shows the board and every change. Drive the app
over IPC, menu commands and UI Automation; frames captured with `kicad_screenshot_all` are
background too.
On a box that is yours (a dedicated test or demo machine), the OpenGL canvas in the foreground
gives the better picture: open with `foreground:true` and a reason, maximize, record the
monitor. Use the foreground only when it benefits the recording and nobody is working on that
screen; the background path is the default.
## Publishing
Push the MP4 and poster to the page and embed with a `<video>` tag whose `src` is
`/blob/app/<page>/docs/videos/<file>.mp4`, with a plain "Watch" link to the
`/api/pages/<owner>/<page>/files/...` URL for phones. Confirm the URL serves with curl before
telling anyone it is live.