# FFmpeg Editing Patterns

Use these as starting points. Replace placeholders, then adapt stream mapping and codecs to the actual files.

## Inspect Media

```bash
ffprobe -hide_banner input.mp4
ffprobe -v error -show_streams -show_format input.mp4
```

## Trim One Clip

Fast trim with stream copy and keyframe-aligned boundaries:

```bash
ffmpeg -ss 00:01:10 -to 00:01:25 -i input.mp4 -c copy -movflags +faststart clip.mp4
```

Frame-accurate trim with re-encode:

```bash
ffmpeg -i input.mp4 \
  -vf "trim=start=70:end=85,setpts=PTS-STARTPTS" \
  -af "atrim=start=70:end=85,asetpts=PTS-STARTPTS" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  -movflags +faststart \
  clip.mp4
```

## Concatenate Compatible Clips

Use concat demuxer when all clips match:

```text
file 'part1.mp4'
file 'part2.mp4'
file 'part3.mp4'
```

```bash
ffmpeg -f concat -safe 0 -i list.txt -c copy merged.mp4
```

## Concatenate After Per-Clip Edits

Use concat filter when clips need trimming, scaling, speed changes, or come from mixed sources:

```bash
ffmpeg -i a.mp4 -i b.mp4 \
  -filter_complex "\
    [0:v]scale=1080:1920,fps=30[v0]; \
    [0:a]aformat=sample_rates=48000:channel_layouts=stereo[a0]; \
    [1:v]scale=1080:1920,fps=30[v1]; \
    [1:a]aformat=sample_rates=48000:channel_layouts=stereo[a1]; \
    [v0][a0][v1][a1]concat=n=2:v=1:a=1[v][a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  merged.mp4
```

For long graphs, move the filter graph into a file and use `-filter_complex_script`.

## Replace Audio on a Video

```bash
ffmpeg -i video.mp4 -i music.m4a \
  -map 0:v:0 -map 1:a:0 \
  -c:v copy -c:a aac -b:a 192k \
  -shortest \
  video-with-audio.mp4
```

## Mix Source Audio and Background Music

Lower the music and mix it under the original track:

```bash
ffmpeg -i video.mp4 -i music.mp3 \
  -filter_complex "\
    [1:a]volume=0.18[music]; \
    [0:a][music]amix=inputs=2:duration=first:dropout_transition=2[a]" \
  -map 0:v:0 -map "[a]" \
  -c:v copy -c:a aac -b:a 192k \
  -movflags +faststart \
  mixed.mp4
```

For speech-over-music, prefer ducking:

```bash
ffmpeg -i voice.wav -i music.wav \
  -filter_complex "\
    [1:a][0:a]sidechaincompress=threshold=0.03:ratio=10:attack=20:release=300[ducked]" \
  -map "[ducked]" \
  -c:a aac -b:a 192k \
  ducked.m4a
```

## Burn Captions Into Video

SRT:

```bash
ffmpeg -i input.mp4 \
  -vf "subtitles=captions.srt" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a copy \
  capped.mp4
```

ASS for styled captions:

```bash
ffmpeg -i input.mp4 \
  -vf "ass=captions.ass" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a copy \
  capped.mp4
```

For `9:16` short-form captions, do not treat `1080x1920` as sufficient by itself. Use layout rules:

- keep captions in the lower-middle safe zone, not on the bottom UI edge
- break long phrases into `2-3` short lines
- keep line width narrow enough to avoid touching the left and right edges
- prefer explicit `\pos(x,y)` placement in ASS for repeatable vertical layouts
- use bold text, thick outline, and minimal shadow instead of placing text too low

## Mux Soft Captions

```bash
ffmpeg -i input.mp4 -i captions.srt \
  -map 0:v -map 0:a? -map 1:0 \
  -c:v copy -c:a copy -c:s mov_text \
  output-with-subs.mp4
```

## Make a Vertical `9:16` Version

Center crop:

```bash
ffmpeg -i input.mp4 \
  -vf "scale=-2:1920,crop=1080:1920" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  vertical.mp4
```

Add slight motion for a more intentional short-form feel:

```bash
ffmpeg -i input.mp4 \
  -vf "scale=-2:1920,crop=1080:1920:x='min(max((iw-ow)/2+36*sin(0.9*t)\,0)\,iw-ow)':y='(ih-oh)/2',fps=30,setsar=1" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  vertical-dynamic.mp4
```

Keep whole frame with padding:

```bash
ffmpeg -i input.mp4 \
  -vf "scale=1080:-2,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  vertical-padded.mp4
```

## Add Transitions Between Clips

Use short transitions unless the brief calls for a slower dramatic feel.

Simple crossfade:

```bash
ffmpeg -i clip1.mp4 -i clip2.mp4 \
  -filter_complex "\
    [0:v]scale=1080:1920,fps=30,setsar=1[v0]; \
    [0:a]aformat=sample_rates=48000:channel_layouts=stereo[a0]; \
    [1:v]scale=1080:1920,fps=30,setsar=1[v1]; \
    [1:a]aformat=sample_rates=48000:channel_layouts=stereo[a1]; \
    [v0][v1]xfade=transition=fade:duration=0.20:offset=2.80[v]; \
    [a0][a1]acrossfade=d=0.20:c1=tri:c2=tri[a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  transitioned.mp4
```

Fade to white for impact or flash-style transitions:

```bash
ffmpeg -i clip1.mp4 -i clip2.mp4 \
  -filter_complex "\
    [0:v]scale=1080:1920,fps=30,setsar=1[v0]; \
    [0:a]aformat=sample_rates=48000:channel_layouts=stereo[a0]; \
    [1:v]scale=1080:1920,fps=30,setsar=1[v1]; \
    [1:a]aformat=sample_rates=48000:channel_layouts=stereo[a1]; \
    [v0][v1]xfade=transition=fadewhite:duration=0.12:offset=3.18[v]; \
    [a0][a1]acrossfade=d=0.12:c1=tri:c2=tri[a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  flash-transition.mp4
```

Slide transition:

```bash
ffmpeg -i clip1.mp4 -i clip2.mp4 \
  -filter_complex "\
    [0:v]scale=1080:1920,fps=30,setsar=1[v0]; \
    [0:a]aformat=sample_rates=48000:channel_layouts=stereo[a0]; \
    [1:v]scale=1080:1920,fps=30,setsar=1[v1]; \
    [1:a]aformat=sample_rates=48000:channel_layouts=stereo[a1]; \
    [v0][v1]xfade=transition=slideleft:duration=0.18:offset=2.82[v]; \
    [a0][a1]acrossfade=d=0.18:c1=tri:c2=tri[a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  slide-transition.mp4
```

If a slide or wipe transition shows a 1-pixel edge artifact, add:

```text
fillborders=left=2:right=2:top=0:bottom=0:mode=mirror
```

immediately after `xfade` to hide leaked edge pixels from the transition stage.

`offset` is when the transition starts on the first clip timeline. A common rule is:

```text
offset = clip1_duration - transition_duration
```

For more than two clips, use a filter script and chain `xfade` stages instead of trying to keep the entire graph inline.

## Add Text

```bash
ffmpeg -i input.mp4 \
  -vf "drawtext=fontfile=/path/font.ttf:text='HOOK LINE':fontsize=84:fontcolor=white:borderw=6:bordercolor=black:x=(w-text_w)/2:y=h*0.75" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a copy \
  titled.mp4
```

Verify the font path before using `drawtext`.

## Overlay a Logo or Image

```bash
ffmpeg -i input.mp4 -i logo.png \
  -filter_complex "[0:v][1:v]overlay=W-w-40:40[v]" \
  -map "[v]" -map 0:a? \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a copy \
  branded.mp4
```

## Speed Up or Slow Down

Double speed:

```bash
ffmpeg -i input.mp4 \
  -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  fast.mp4
```

Half speed:

```bash
ffmpeg -i input.mp4 \
  -filter_complex "[0:v]setpts=2.0*PTS[v];[0:a]atempo=0.5[a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  slow.mp4
```

Chain `atempo` filters when the factor falls outside `0.5-2.0`.

## Extract One Frame

```bash
ffmpeg -ss 00:00:12.500 -i input.mp4 -frames:v 1 frame.jpg
```

## Extract Audio Only

```bash
ffmpeg -i input.mp4 -vn -c:a copy audio.m4a
```

Re-encode when the target container or codec requires it.

## Export Rules of Thumb

- Use `-map` explicitly for non-trivial outputs.
- Use `-movflags +faststart` for MP4 delivery.
- Use `-pix_fmt yuv420p` for broad playback compatibility.
- Use `-r`, `fps`, or both intentionally. Do not change frame rate casually.
- Use `aformat` before concat when audio layouts differ.
- Use `setsar=1` after scale/crop when the output should have square pixels.
- Prefer a filter script file when quoting becomes fragile.
