agentsclimarketplace

Ffmpeg cut concat

Skill damionrashford/media-os/skills/ffmpeg-cut-concat

Trim, cut, split, segment, and concatenate media with ffmpeg (stream copy when possible, re-encode across cut boundaries). Use when the user asks to trim a video, cut a clip, extract a segment by timestamps, remove a section, split into parts, join/merge videos, concatenate files, or build a segmented HLS-style playlist.From its SKILL.md

Install
npx -y skills add damionrashford/media-os --skill ffmpeg-cut-concat

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 17 stars17 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

9.7 KB, as published. Nobody here has run it

Ffmpeg Cut Concat

Context: $ARGUMENTS

Quick start

  • Fast trim (keyframe-aligned): → Step 1 → trim mode with -c copy
  • Frame-accurate trim: → Step 1 → trim mode, re-encode (--accurate)
  • Split one file into N pieces: → Step 1 → segment mode
  • Join same-codec files: → Step 1 → concat-copy mode
  • Join different-codec files: → Step 1 → concat-filter mode

When to use

  • Extracting a clip between two timestamps (e.g. 00:01:30 to 00:02:00).
  • Splitting a long file into N-second chunks (podcast, HLS prep).
  • Joining multiple recordings end-to-end.
  • Removing the first/last N seconds of a video.
  • Building a highlight reel from several source files.

Step 1 — Pick a strategy

GoalModeCommand skeleton
Trim one clip from one filetrimffmpeg -ss T1 -i in -to T2 -c copy out
Split one file into equal piecessegmentffmpeg -i in -f segment -segment_time N out_%03d.mp4
Join files with identical codecsconcat-copyffmpeg -f concat -safe 0 -i list.txt -c copy out
Join files with mismatched codecsconcat-filter-filter_complex "concat=n=N:v=1:a=1"

Do not use concat demuxer for files that differ in codec, resolution, SAR, fps, or sample rate — it will mux them but playback breaks silently. Use the concat filter instead.

Step 2 — Decide stream copy vs re-encode

  • Stream copy (-c copy) is ~100x faster and lossless, but cuts land on the nearest keyframe before the requested timestamp. A clip asked for at 00:01:30.000 may actually start at 00:01:28.5.
  • Re-encode (drop -c copy, add -c:v libx264 -c:a aac) is frame-accurate but slow and lossy.
  • Default rule: start with -c copy; switch to re-encode only when the user says "frame-accurate," "exact," or the first frame looks wrong after a test.

Input-side vs output-side seek:

  • -ss T -i in.mp4 (before -i) — input seek. Fast; ffmpeg seeks in the container to the keyframe at or before T, then starts decoding. With -c copy, output begins at that keyframe.
  • -i in.mp4 -ss T (after -i) — output seek. Decodes from the start, discards frames until T. Slow but accurate. Rarely needed since modern ffmpeg does accurate input seek by default with -accurate_seek.

For accurate cut with re-encode, use input seek too — it is fast AND accurate once decoding is on.

Step 3 — Run the command

3a. Trim (stream copy, keyframe-aligned)

ffmpeg -ss 00:01:30 -i input.mp4 -to 00:02:00 -c copy -avoid_negative_ts make_zero out.mp4

-to is absolute input-time. If you wrote -ss 00:01:30 -to 00:02:00, you get 30 seconds out.

Use -t 30 instead of -to to specify duration directly (-t wins if both set).

3b. Trim (frame-accurate, re-encode)

ffmpeg -ss 00:01:30 -i input.mp4 -to 00:02:00 \
  -c:v libx264 -crf 18 -preset veryfast \
  -c:a aac -b:a 192k \
  out.mp4

3c. Segment (split into N-second pieces)

ffmpeg -i input.mp4 -c copy -map 0 \
  -f segment -segment_time 60 -reset_timestamps 1 \
  out_%03d.mp4

Segments still break on keyframes. If you need exact 60.000s pieces, re-encode with forced keyframes: -force_key_frames "expr:gte(t,n_forced*60)".

3d. Concat demuxer (same codec params)

Create list.txt:

file 'clip1.mp4'
file 'clip2.mp4'
file 'clip3.mp4'

Run:

ffmpeg -f concat -safe 0 -i list.txt -c copy out.mp4

-safe 0 allows absolute paths and .. — needed for anything except plain filenames in CWD.

3e. Concat filter (different codecs / resolutions)

ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 \
  -filter_complex "[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1[v][a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 18 -c:a aac -b:a 192k \
  out.mp4

Each input contributes v video streams and a audio streams — reference them in order [i:v][i:a] for i=0..n-1.

Step 4 — Verify duration

ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 out.mp4

For stream copy, the reported duration may be a bit shorter or longer than requested due to keyframe alignment. If the discrepancy is more than ~2s, your GOP size is large — re-encode.

Available scripts

  • scripts/cut.py — single entrypoint for all four modes (trim, segment, concat-copy, concat-filter) with --dry-run and --verbose.

Workflow

# Fast trim
uv run ${CLAUDE_SKILL_DIR}/scripts/cut.py trim \
  --input in.mp4 --start 00:01:30 --end 00:02:00 --output clip.mp4

# Frame-accurate
uv run ${CLAUDE_SKILL_DIR}/scripts/cut.py trim \
  --input in.mp4 --start 00:01:30 --end 00:02:00 --accurate --output clip.mp4

# Split into 60s chunks
uv run ${CLAUDE_SKILL_DIR}/scripts/cut.py segment \
  --input in.mp4 --seconds 60 --pattern out_%03d.mp4

# Concat same-codec files
uv run ${CLAUDE_SKILL_DIR}/scripts/cut.py concat-copy \
  --inputs a.mp4 b.mp4 c.mp4 --output joined.mp4

# Concat mixed-codec files (re-encodes)
uv run ${CLAUDE_SKILL_DIR}/scripts/cut.py concat-filter \
  --inputs a.mp4 b.webm --output joined.mp4

Reference docs

  • Read references/patterns.md for exact list.txt escaping rules, the two-pass -ss recipe, -copyts muxing-accurate cuts, and keyframe inspection via ffprobe.

Gotchas

  • Concat demuxer is silent about mismatches. Files MUST have identical codec, resolution, SAR, pixel format, fps, sample rate, and channel layout. Otherwise the output plays wrong (frozen video, desync audio) with zero warnings. Probe first: ffprobe -v error -show_streams a.mp4 b.mp4 and diff the codec/fps/sar fields.
  • -safe 0 is required for concat list entries with absolute paths or ... Without it you get Unsafe file name.
  • Single-quote escaping in list.txt. Each path is wrapped in '...'. To include a literal ', close the quote, backslash-escape, reopen: file 'it'\''s.mp4'.
  • Input seek + -c copy can produce negative timestamps. The first packet's PTS may be earlier than the cut point. Add -avoid_negative_ts make_zero or players may show a blank first second.
  • Stream-copy trim starts on a keyframe. If you asked for 00:01:30 but the nearest prior keyframe is at 00:01:28.5, the output starts at 00:01:28.5. To snap exactly, re-encode.
  • -to is absolute, -t is duration. -ss 60 -to 90 = 30s clip. -ss 60 -t 90 = 90s clip. Mixing them confuses people who memorized one form.
  • Concat filter requires matching stream counts per input. concat=n=3:v=1:a=1 expects [0:v][0:a][1:v][1:a][2:v][2:a]. A silent input needs a dummy anullsrc or a=0.
  • Lossless cuts only work at keyframes. Run ffprobe -select_streams v -show_frames -skip_frame nokey -show_entries frame=pkt_pts_time to find them.
  • -ss position before -i on some formats (especially raw/TS) seeks inaccurately. If the clip starts in the wrong place, move -ss after -i to force decode-based seek.

Examples

Example 1: cut a 30s clip, fast

ffmpeg -ss 00:01:30 -i lecture.mp4 -to 00:02:00 \
  -c copy -avoid_negative_ts make_zero \
  highlight.mp4

Example 2: remove first 10 seconds, frame-accurate

ffmpeg -ss 10 -i raw.mov \
  -c:v libx264 -crf 18 -preset veryfast -c:a aac -b:a 192k \
  trimmed.mp4

Example 3: split a podcast into 10-minute chunks

ffmpeg -i podcast.m4a -c copy -f segment -segment_time 600 \
  -reset_timestamps 1 part_%02d.m4a

Example 4: join three identical-format clips

cat > list.txt <<'EOF'
file 'intro.mp4'
file 'main.mp4'
file 'outro.mp4'
EOF
ffmpeg -f concat -safe 0 -i list.txt -c copy final.mp4

Example 5: merge a 1080p mp4 and a 720p webm

ffmpeg -i a.mp4 -i b.webm \
  -filter_complex "[0:v]scale=1280:720,setsar=1[v0];[1:v]setsar=1[v1];[v0][0:a][v1][1:a]concat=n=2:v=1:a=1[v][a]" \
  -map "[v]" -map "[a]" -c:v libx264 -crf 20 -c:a aac mix.mp4

Troubleshooting

Error: Unsafe file name

Cause: concat demuxer refusing a path outside CWD without -safe 0. Solution: add -safe 0 before -i list.txt, or use filenames relative to CWD.

Output starts before the requested -ss, or has a black first frame

Cause: keyframe alignment with -c copy + negative initial PTS. Solution: add -avoid_negative_ts make_zero. For exact start, drop -c copy and re-encode.

Concat demuxer output has frozen video or audio desync

Cause: input files differ in codec/resolution/fps/sar/sample rate. Solution: probe them (ffprobe -show_streams), confirm mismatch, switch to concat filter with re-encode, or normalize inputs first with -c:v libx264 -vf "scale=W:H,fps=F,setsar=1" -ar 48000 -ac 2.

Stream specifier ':a' matches no streams

Cause: one input has no audio track, but your concat filter expects a=1. Solution: either generate silence (-f lavfi -i anullsrc=cl=stereo:r=48000) for that input, or set a=0 and handle audio separately.

Segments are uneven lengths despite -segment_time 60

Cause: segmenter only breaks at keyframes; your GOP is longer than 60s or irregular. Solution: re-encode with -force_key_frames "expr:gte(t,n_forced*60)" before segmenting (or in the same pass).

[concat @ ...] DTS ... < ... out of order

Cause: overlapping or decreasing timestamps when concatenating. Solution: add -fflags +genpts before -i list.txt, or re-mux each input through -c copy first to normalize timestamps.

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.