stitch-videos-ffmpeg
Stitch video segments with ffmpeg concat, xfade, overlay, audio mux, and export settings, and trim a source into named clips at exact windows (trim_clips.py). Ships montage.py, a free montage assembler (python3 + ffmpeg, no keys).
Install / Use
npx skills add gooseworks-ai/goose-skills --skill stitch-videos-ffmpegInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
OtherSupported Platforms
Our assessment of stitch-videos-ffmpeg
stitch-videos-ffmpeg scores 93/100 on our quality scale, 25th of 283 Other skills we index (top 9%).
Its SKILL.md is 13 KB long, well organised into 17 sections with 5 code examples: a thorough specification that gives an agent plenty to work with.
With 1,222 GitHub stars, it is one of the more widely adopted skills in the catalogue.
Maintenance, license and trust
- The repository was last updated 33 days ago, so stitch-videos-ffmpeg is actively maintained.
- It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
- Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
Safety scan
No issues foundOur scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands.
Automated pattern scan on 2026-10-26. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.
stitch-videos-ffmpeg compared with similar skills
All 4 of these similar skills score higher than stitch-videos-ffmpeg; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| stitch-videos-ffmpeg (this skill)by gooseworks-ai | 93 | 1.2k | 33d ago | SKILL.md |
| Agent-Reachby Panniantong | 100 | 102.4k | 1d ago | CLAUDE.md |
| headroomby headroomlabs-ai | 100 | 77.8k | 1d ago | CLAUDE.md |
| Scraplingby D4Vinci | 100 | 89.7k | 3d ago | MCP Server |
| crawl4aiby unclecode | 100 | 86.3k | 3d ago | MCP Server |
Frequently asked questions
- How do I install stitch-videos-ffmpeg?
- Run
npx skills add gooseworks-ai/goose-skills --skill stitch-videos-ffmpeg. The install tabs above show the steps for each supported agent. - Which AI agents does stitch-videos-ffmpeg work with?
- It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
- Is stitch-videos-ffmpeg safe to use?
- Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. It is MIT-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is stitch-videos-ffmpeg still maintained?
- The repository was last updated 33 days ago, so stitch-videos-ffmpeg is actively maintained.
Skill content
View source on GitHubname: stitch-videos-ffmpeg description: Stitch video segments with ffmpeg concat, xfade, overlay, audio mux, and export settings, and trim a source into named clips at exact windows (trim_clips.py). Ships montage.py, a free montage assembler (python3 + ffmpeg, no keys). It takes a JSON EDL of clips and stills, normalizes them and hard-cuts them in order, burns captions from an SRT, a cue list or word timings, and lays a VO over a music bed that ducks under it, mastered to -14 LUFS. version: "1.3.1"
stitch-videos-ffmpeg
Purpose
Stitch video segments with ffmpeg concat, xfade, overlay, audio mux, and export settings.
Implementation status: refactored from existing repository skills. The workflow consolidates behavior that previously lived across larger skills.
Sources: ad-studio, voiceover-product-ad, ugc-product-video, voiceless-music-transformation-reel, product-stopmotion-ad.
Extraction notes: assembly and composite scripts.
Montage helper: scripts/montage.py
A free, deterministic way to cut a montage ad from finished clips. It needs python3 and
ffmpeg/ffprobe only (Pillow is optional, see Captions). It makes no network or provider
calls and needs no keys, so re-cuts cost nothing.
| Step | What it does |
|---|---|
| edl | Checks a JSON spec and writes edl.json. Every file must exist and is probed with ffprobe; every cut must fit inside its clip. All problems are listed at once. |
| assemble | Scales each cut to one size (default 1080×1920, cover crops to fill, contain pads), one frame rate (default 30), square pixels and yuv420p. Then it hard-cuts them in order with the filter_complex concat filter, never the -f concat demuxer. Each cut gets an exact frame count, so the output length equals the EDL total. |
| captions | Burns an SRT, a JSON cue list, or word timings (one cue per --per words) into the video. The cue text is kept exactly as given (wrapping only turns spaces into line breaks). |
| mix | Lays the VO over the video and adds an optional music bed that ducks under the VO (sidechain compression, 20:1). Then it masters to -14 LUFS / -1 dBTP with two-pass loudnorm. |
| run | Runs all four steps from one spec and writes manifest.json. |
Each step prints a one-line JSON summary. Exit codes: 0 ok, 1 ffmpeg failed, 2 bad
spec or arguments, 3 a tool is missing (ffmpeg, ffprobe, or a caption renderer).
Run it
After gooseworks fetch stitch-videos-ffmpeg (or as a dependency of a format package), the
scripts are in /tmp/gooseworks-scripts/stitch-videos-ffmpeg/scripts/:
S=/tmp/gooseworks-scripts/stitch-videos-ffmpeg/scripts # or this folder's scripts/ in a checkout
python3 $S/montage.py run --spec montage.json --out edits/master.mp4 --workdir edits/work
# or one step at a time
python3 $S/montage.py edl --spec montage.json --out edits/edl.json
python3 $S/montage.py assemble --edl edits/edl.json --out edits/body.mp4
python3 $S/montage.py captions --video edits/body.mp4 --words audio/vo.words.json \
--respell '{"symbiotic": "synbiotic"}' --color "#FFE800" --out edits/captioned.mp4
python3 $S/montage.py mix --video edits/captioned.mp4 --vo audio/vo.mp3 \
--music audio/bed.mp3 --out edits/master.mp4
Spec format
Paths are relative to the spec file. Times are seconds or a timecode string ("SS",
"MM:SS", "HH:MM:SS", optional .ms).
{
"output": {"width": 1080, "height": 1920, "fps": 30, "fit": "cover"},
"words": "audio/vo.words.json",
"clips": [
{"file": "clips/scene-01.mp4", "label": "hook", "word_range": [0, 4]},
{"file": "clips/scene-02.mp4", "label": "feature", "in": 0.2, "out": 1.6},
{"file": "clips/scene-03.mp4", "label": "reaction", "in": "00:00.5", "duration": 0.9},
{"file": "clips/scene-04.mp4", "label": "b-roll", "t_in": 4.1, "t_out": 5.0},
{"file": "clips/scene-05.mp4"},
{"file": "overlays/landing-page.png", "label": "landing-page", "duration": 1.5,
"pan": {"from": {"x": 0.5, "y": 0.2, "zoom": 1.0}, "to": {"x": 0.5, "y": 0.7, "zoom": 1.6}}},
{"file": "brand/end-card.png", "label": "end-card", "duration": 2.0}
],
"clip_audio": "drop",
"captions": {"words": "audio/vo.words.json", "per": 1, "respell": {"symbiotic": "synbiotic"},
"style": {"color": "#FFE800", "y": 0.5}},
"audio": {"vo": "audio/vo.mp3", "music": "audio/bed.mp3", "music_start": 14.2}
}
How long each cut is (first match wins):
word_range: [i, j]cuts on the VO's word boundaries. It needs a top-levelwordsfile, used as the transcriber wrote it: a flat[{text|word, start, end}]list,{words: [...]}(OpenAI, ElevenLabs;spacingentries are skipped), Whisper's{segments: [{words: [...]}]}(what fal's Whisper returns), or fal's{chunks: [{text, timestamp: [s, e]}]}. Words with no time are skipped, and leading spaces are stripped. The cut runs from the start of wordito the start of wordj + 1, or to the last word's end. The first cut starts at 0 so it covers the lead-in. Consecutive ranges tile the VO with no gaps.t_in/t_outgive a window on the timeline. The cut ist_out - t_inlong. A window that does not start where the previous cut ended is reported as a gap or overlap warning;--strictturns warnings into errors.in+out, orin+duration, trim the source clip.- With nothing set, the whole clip plays (from
in, default 0).
Every cut starts at in in its source (default 0). A still image (.png, .jpg, .webp)
needs a length; pan zooms and pans across it. The still is first cover-cropped to the output
aspect (9:16 by default), and x/y are the window centre as a fraction of that cropped image
(zoom ≥ 1). A video clip may be up to 0.1 s short of its cut, and then its
last frame is held. Anything shorter is an error.
clip_audio (--clip-audio on assemble): drop (default) or keep. keep keeps each
clip's own sound and fills silence under stills and silent clips.
Captions
- Sources:
srt,cues(a list or a JSON file of{start, end, text}), orwords(any of the word-file shapes above) plusperand an optionalrespellmap.respellswaps a misheard word for the locked spelling and keeps the punctuation around it. Overlapping cues: the later one wins. - Look: each cue is drawn whole, in one colour with an outline. With
per: 1that is one word at a time, each held until the next. There is no active-word (karaoke) highlight. - Timing: a cue shows from the first output frame at or after its start until the first frame at or after its end (a 1 ms caption grid; ffmpeg older than 5 falls back to 1/25 s and says so in the step's warnings).
- Style:
font,font_size(px, default 4.5% of the height),color,outline_color,outline(px),y(centre of the caption block as a fraction of the height, default 0.72) andmax_width(default 0.86 of the width). - Renderers:
autouses Pillow when it is installed, so captions look the same on every machine. Otherwise it uses libass, which needs an ffmpeg with theassfilter. Some ffmpeg builds (Homebrew's default) have no libass and nodrawtext, so Pillow is the dependable renderer. With neither, the step exits 3 and says so.
Mix defaults (all overridable)
| Setting | Default | Meaning |
|---|---|---|
| vo_lufs | -16 | VO level before mixing (static gain from a measured pass) |
| music_lufs | -24 | Bed level between VO lines, before ducking |
| duck_threshold / duck_ratio | 0.02 / 20 | The bed ducks while the VO is above the threshold. ffmpeg caps the ratio at 20. |
| duck_attack / duck_release | 20 / 400 ms | How fast the bed dips and comes back |
| vo_start, music_start | 0 | Where each track enters on the timeline. vo_start moves only the VO audio, not word_range cuts or word captions, so keep it 0 when those come from the same VO. |
| music_fade_in / music_fade_out | 0 / 1 s | A bed shorter than the video loops (with a warning) |
| target_lufs / target_tp | -14 / -1 | Master loudness. off skips it. |
| keep_video_audio | false | Mix the video's own audio in too (not ducked) |
A VO that runs past the end of the video is cut and reported as a warning, so lengthen the EDL (for example, the end card) instead.
Tests
In the goose-skills repo (in the goose-skills repo only; installed copies have no tests/ folder), tests/test_stitch_montage.py makes tiny synthetic clips, a still and two tones with ffmpeg,
then checks: the output length equals the EDL total to the frame, size/fps/pixel shape are
normalized, captions appear only on their cue frames with the exact text, the bed ducks
under the VO (about 14 dB on the fixture), and the master is at -14 ±1 LUFS. It runs in CI
(media-tests) and locally:
python3 -m pytest -q skills/video/stitch-videos-ffmpeg/tests
Related
- The older scripts here (
composite.py,composite_final.py/.sh,normalize_clip.sh,voiceless/composite.py) are unchanged. mix-masterremains the multi-clip VO + SFX mix. Amontage.pymaster is already at -14 LUFS, so a later -14 LUFS finishing pass barely changes it.caption-burnremains the place for plate, seam and hook-card caption styles.
Trim mode: scripts/trim_clips.py
Cuts one source video into named clips at exact windows (this replaces the studio's trim-video-clips). Each clip is re-encoded with libx264, so cuts land on the requested frames and drop cleanly into an edit. Deciding which windows to cut is the caller's job.
python3 scripts/trim_clips.py --source source.mp4 --clips clips.json --output-dir clips/ [--crf 18] [--overwrite]
clips.json is a list of {name, start, end} or {name, start, duration}; times are seconds or
SS, MM:SS, HH:MM:SS with optional .ms:
[
{"name": "hook-laptop-close", "start": 0, "end": 2.6},
{"name": "mac-mini-glow", "start": "00:03", "duration": 5}
]
- One
<slug>.mp4per clip, andmanifest.jsonwith each clip's window, real duration and status. - A bad spec (no end or duration, a window that ends before it starts) is skipped and listed in
errors; the other clips still cut. A window that runs past the end of the source gives a shorter clip and a warning with both lengths. - Re-runs skip clips that already exist unless
--overwrite. A source with no audio gives clips with no audio.
FFmpeg notes
Measured traps when joining and finishing. Each lives here once.
- Joined generated clips drift in colour at every cut, even from one prompt. Apply one
harmonising grade over the whole joined video;
eq=contrast=1.05:saturation=0.95,colorbalance=bs=0.05:bm=-0.02is a starting point. - A still PNG overlay with a fade does nothing unless the input is looped:
-loop 1 -framerate 30 -t <length>before that-i. The build reports no error. - ffmpeg 9 removed
-vsync. The old flag fails the whole command; use-fps_mode, or drop it (-update 1alone overwrites frame by frame). -ssbefore-ican land on a keyframe (always with stream copy). A re-encoded cut is exact; a measurement a decision depends on puts-ssafter-ior decodes the range.croprounds offsets to whole pixels, so a gentle sine move becomes a still with one-pixel snaps. Scale up 4x, crop, scale back. Judge motion on the median, not the p90.selectthentiletakes consecutive frames. Seek with-ssbefore the input, thenfps=N, thentile.- A trailing comma in
filter_complexfails the build silently behind a captured subprocess, and an older render on disk then passes the checks. Strip trailing commas and read the build's own exit status. - Generated video is already sharp. A finishing sharpen adds the artefact it means to hide; keep unsharp near 0.12 with no grain, and measure before assuming footage is soft.
Inputs
- A clear user
Truncated for display — read the full file on GitHub.
Related Skills
Agent-Reach
102.4kGive your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees.
headroom
77.8kCompress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.
Scrapling
89.7k🕷️ A Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev
crawl4ai
86.3kOpen-source web crawler and scraper for LLMs and AI agents: any website into clean, LLM-ready Markdown. Run it yourself, or use Crawl4AI Cloud with one key. Join our Discord https://discord.gg/VuxzM8mtf
Languages
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
