ffmpeg-cli
FFmpeg CLI reference for video and audio processing, format conversion, filtering, and media automation. Use when converting video formats, resizing or cropping video, trimming by time, replacing or extracting audio, mixing audio tracks, overlaying text or images, burning subtitles, creating GIFs, generating thumbnails, building slideshows, changing playback speed, encoding with H264/H265/VP9, setting CRF/bitrate, using GPU acceleration, creating storyboards, or running ffprobe. Covers filter_complex, stream selectors, -map, -c copy, seeking, scale, pad, crop, concat, drawtext, zoompan, xfade.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install henkisdabro-wookstar-claude-code-plugins-ffmpeg-cli
Repository
Skill path: plugins/ffmpeg/skills/ffmpeg-cli
FFmpeg CLI reference for video and audio processing, format conversion, filtering, and media automation. Use when converting video formats, resizing or cropping video, trimming by time, replacing or extracting audio, mixing audio tracks, overlaying text or images, burning subtitles, creating GIFs, generating thumbnails, building slideshows, changing playback speed, encoding with H264/H265/VP9, setting CRF/bitrate, using GPU acceleration, creating storyboards, or running ffprobe. Covers filter_complex, stream selectors, -map, -c copy, seeking, scale, pad, crop, concat, drawtext, zoompan, xfade.
Open repositoryBest for
Primary workflow: Write Technical Docs.
Technical facets: Full Stack, Tech Writer.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: henkisdabro.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install ffmpeg-cli into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/henkisdabro/wookstar-claude-code-plugins before adding ffmpeg-cli to shared team environments
- Use ffmpeg-cli for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: ffmpeg-cli
description: FFmpeg CLI reference for video and audio processing, format conversion, filtering, and media automation. Use when converting video formats, resizing or cropping video, trimming by time, replacing or extracting audio, mixing audio tracks, overlaying text or images, burning subtitles, creating GIFs, generating thumbnails, building slideshows, changing playback speed, encoding with H264/H265/VP9, setting CRF/bitrate, using GPU acceleration, creating storyboards, or running ffprobe. Covers filter_complex, stream selectors, -map, -c copy, seeking, scale, pad, crop, concat, drawtext, zoompan, xfade.
allowed-tools: Bash, Read, Glob, Grep, Write, Edit
---
# FFmpeg CLI Reference
## Contents
- [Dependencies](#dependencies)
- [Glossary of flags and filters](#glossary-of-flags-and-filters)
- [Converting formats](#converting-formats)
- [Resizing and padding](#resizing-and-padding)
- [Trim by time](#trim-by-time)
- [Verification and inspection](#verification-and-inspection)
- [Reference files](#reference-files)
## Dependencies
Verify ffmpeg is installed:
```sh
ffmpeg -version
```
Install if missing:
```sh
# macOS
brew install ffmpeg
# Ubuntu/Debian
sudo apt install ffmpeg
# Windows (scoop)
scoop install ffmpeg
```
## Glossary of flags and filters
[Filtering reference](https://ffmpeg.org/ffmpeg-filters.html#Filtering-Introduction)
| Flag/Filter | Purpose |
|---|---|
| `-vf` (also `-filter:v`) | Video filter |
| `-af` (also `-filter:a`) | Audio filter |
| `-filter_complex` | Complex filter graph for multi-stream filtering |
| `[0]` | All streams from first input (0-based) |
| `[0:v]` | Video stream from first input |
| `[1:a]` | Audio stream from second input |
| `0:v:0` | First input, first video stream |
| `0:a:1` | First input, second audio stream |
| `[name]` | Named stream, used with `-filter_complex` |
| `-map [name]` | [Select stream for output](https://trac.ffmpeg.org/wiki/Map) |
| `-y` | Auto-overwrite output files without confirmation |
[Expression evaluations](https://ffmpeg.org/ffmpeg-all.html#Expression-Evaluation): `if`, `lte`, `gte` and more.
## Converting formats
Remux MP4 to MKV (no re-encoding):
```sh
ffmpeg -i input.mp4 -c copy output.mkv
```
MKV and MP4 are both containers for H264/H265 video and AAC/MP3 audio. Video quality is determined by the codec, not the container. MKV supports multiple video streams; MP4 has wider device support.
Remux MP4 to MOV:
```sh
ffmpeg -i input.mp4 -c copy output.mov
```
Encode MP4 to AVI (re-encodes):
```sh
ffmpeg -i input.mp4 output.avi
```
## Resizing and padding
Upscale to 1080x1920, preserve aspect ratio, black padding:
```sh
ffmpeg -i input.mp4 -vf "scale=w=1080:h=1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1:1" output.mp4
```
**scale** options:
- `force_original_aspect_ratio=decrease` - auto-decrease output dimensions to fit aspect ratio
- `force_original_aspect_ratio=increase` - auto-increase dimensions
- `scale=w=1080:h=-1` - let ffmpeg pick correct height for aspect ratio
- `scale=w=1080:h=-2` - force dimensions divisible by 2
[scale reference](https://trac.ffmpeg.org/wiki/Scaling)
**pad** options:
- `pad=width:height:x:y:color` - x:y is top-left corner position
- `(ow-iw)/2:(oh-ih)/2` centres the video; negative values also centre
- [pad reference](https://ffmpeg.org/ffmpeg-filters.html#pad)
**setsar=1:1** ensures 1:1 pixel aspect ratio. Prevents ffmpeg from compensating for ratio changes. [setsar reference](https://ffmpeg.org/ffmpeg-filters.html#setdar_002c-setsar)
Two scaled outputs from one input (horizontal + vertical with logo overlay):
```sh
ffmpeg -i input.mp4 -i logo.png \
-filter_complex "[0:v]split=2[s0][s1]; \
[s0]scale=w=1920:h=1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1:1[out1]; \
[s1]scale=w=720:h=1280:force_original_aspect_ratio=decrease,pad=720:1280:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1:1[s2]; \
[s2][1]overlay=(main_w-overlay_w)/2:(main_w-overlay_w)/5[out2]" \
-map [out1] -map 0:a output_youtube.mp4 \
-map [out2] -map 0:a output_shorts.mp4
```
## Trim by time
```sh
ffmpeg -i input.mp4 -ss 00:00:10 -to 00:00:25 output.mp4
```
For faster but less accurate trimming, see the **Input/output seeking** section in `references/encoding-and-settings.md`. For the `-c copy` trade-offs when trimming, see `references/core-concepts.md`.
## Verification and inspection
List supported formats:
```sh
ffmpeg -formats
```
List supported codecs:
```sh
ffmpeg -codecs
```
### ffprobe
[ffprobe](https://ffmpeg.org/ffprobe.html) provides structured metadata about media files.
Show detailed stream information:
```sh
ffprobe -show_streams -i input.mp4
```
Verify moov atom position (faststart):
```sh
ffprobe -v trace -i input.mp4
```
Look for `type:'moov'` near the beginning of output to confirm faststart is applied.
## Reference files
Load these on demand with the Read tool when the task requires deeper coverage.
| File | Topics |
|---|---|
| `references/core-concepts.md` | `-c copy` (remux vs transcode), input/output seeking, encoding quick ref (H264/H265/VP9 CRF ranges), GPU acceleration overview, `format=yuv420p`, `-movflags +faststart` |
| `references/audio-processing.md` | Replace audio, extract audio, mix audio, combine MP3 tracks, crossfade, change audio format, merge and normalise |
| `references/advanced-editing.md` | Playback speed, FPS change, jump cuts, video cropping for social, drawtext overlay, subtitles (burn/embed/extract), combine media (overlay, logo, background, concat intro/main/outro, vstack) |
| `references/asset-generation.md` | Image to video, slideshow with fade, Ken Burns (zoompan), GIFs, video compilation with fades, thumbnails (single/multiple/scene), image thumbnails, storyboards (scene tile/keyframe/Nth frame) |
| `references/encoding-and-settings.md` | Optimised daily command, H264 (libx264) deep-dive, H265 (libx265) Apple compat, VP9 (libvpx-vp9) constant quality, 1-pass vs 2-pass, `-c copy` detailed, seeking detailed, GPU detailed (Nvidia/Intel/AMD) |
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/encoding-and-settings.md
```markdown
# Encoding and Settings
## Optimised daily command
Good for archiving, streaming (non-live), and wide device playback:
```sh
ffmpeg -i input.mp4 \
-vf "scale=w=1080:h=1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1:1" \
-crf 18 -preset veryslow -threads 0 -tune fastdecode -movflags +faststart \
output.mp4
```
| Flag | Purpose |
|---|---|
| `-crf 18` | High quality constant rate factor (see H264 section) |
| [`-preset veryslow`](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) | Slower encoding, better compression for same quality. Use `ultrafast` when speed matters more than size |
| `-threads 0` | Let ffmpeg optimise thread count (default, usually best to omit) |
| [`-tune fastdecode`](https://trac.ffmpeg.org/wiki/Encode/H.264#Tune) | Output requires less compute to decode - good for edge devices. Use `zerolatency` for [fast encoding and low-latency streaming](https://superuser.com/questions/564402/explanation-of-x264-tune) |
| `-movflags +faststart` | Move metadata to front for faster web playback |
## H264 (libx264)
[Full reference](https://trac.ffmpeg.org/wiki/Encode/H.264)
Default for MP4 output. Always specify `-c:v libx264` explicitly.
### CRF (Constant Rate Factor)
Default bitrate control for libx264 and libx265. Best for quality-focused encoding where file size is secondary.
- **Range:** 0-51 (0 = lossless 8-bit, for 10-bit use `-qp 0`)
- **Default:** 23
- **Recommended:** 17-28 (17-18 visually lossless)
- **Exponential scale:** CRF +6 = roughly half bitrate/file size; -6 = roughly double
- Common high-quality: `-crf 18`; better quality: `-crf 10`
> CRF allows the encoder to attempt a certain output quality for the whole file. It adjusts the quantizer for each frame to maintain requested quality. The downside is you cannot target a specific file size or bitrate, so this is not recommended for streaming.
### format=yuv420p
[Reference](https://trac.ffmpeg.org/wiki/Encode/H.264#Encodingfordumbplayers)
H264 YUV planar colour format for broad playback compatibility:
```sh
ffmpeg -i input.mp4 -vf format=yuv420p -c:v libx264 output.mp4
```
Required for QuickTime and most players. Use when converting images to video or experiencing playback issues. Alias: `-pix_fmt yuv420p`.
### -movflags +faststart
[Reference](https://trac.ffmpeg.org/wiki/Encode/H.264#faststartforwebvideo)
Moves metadata to front of container. Supported in MP4, M4A, MOV.
```sh
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
```
Works with both libx264 and libx265:
```sh
ffmpeg -i input.mp4 -c:v libx265 -c:a copy -movflags +faststart output.mp4
```
Verify with ffprobe:
```sh
ffprobe -v trace -i output.mp4
```
Look for `type:'moov'` near the beginning. [YouTube recommends](https://support.google.com/youtube/answer/1722171) faststart for uploads.
## H265 (libx265)
Similar controls to H264. Use `-vtag hvc1` for Apple compatibility (iOS AirDrop):
```sh
ffmpeg -i input.mp4 -c:v libx265 -vtag hvc1 -c:a copy output.mp4
```
## VP9 (libvpx-vp9)
[Full reference](https://trac.ffmpeg.org/wiki/Encode/VP9)
Open, royalty-free format owned by Google. Used by YouTube. Saves 20-50% bitrate vs libx264 at same visual quality.
### Constant quality encoding
Must use `-crf` with `-b:v 0` (setting `-b:v` higher or omitting it invokes Constrained Quality mode instead):
```sh
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 15 -b:v 0 -c:a libopus output.webm
```
- **CRF range:** 0-63 (lower = better)
- **Recommended:** 15-35, with 31 for 1080p
- Default audio encoder: libopus (re-encodes AAC to Opus)
- [CPU/speed/threading controls](https://trac.ffmpeg.org/wiki/Encode/VP9#speed)
## 1-pass vs 2-pass encoding
VP9, libx264, and libx265 all support both modes.
| Use case | Recommended mode |
|---|---|
| Archival | CRF (single pass) |
| Streaming (non-live) | Two-pass CRF or ABR with VBV-constrained bitrate |
| Live streaming | One-pass CRF or ABR with VBV, or CBR |
| Encoding for devices | Two-pass ABR |
Two-pass CRF in VP9 ([reference](https://trac.ffmpeg.org/wiki/Encode/VP9#twopass)): first pass calculates optimal encoding parameters, second pass achieves better compression for web-hosted video.
### Codec flags quick reference
| Flag | Purpose |
|---|---|
| `-c:v` | Video encoder |
| `-c:a` | Audio encoder |
| `-c:a aac` | AAC audio (default, good practice to specify) |
| [`-c:a libmp3lame`](https://trac.ffmpeg.org/wiki/Encode/MP3) | MP3 encoding |
| `-an` | Disable audio output |
## -c copy (stream copy) - detailed
[Reference](https://ffmpeg.org/ffmpeg.html#Streamcopy)
Re-muxes without re-encoding - dramatically faster than transcoding.
| Flag | Purpose |
|---|---|
| `-c copy` | Copy all streams |
| `-c:v copy` | Copy video only |
| `-c:a copy` (or `-acodec copy`) | Copy audio only |
**Remuxing** rewraps streams into a new container without altering them. **Transcoding** changes compression and quality. MP4 can be remuxed to MKV and MOV (all H264 containers).
### When NOT to use -c copy
- Applying video filters (scale, overlay, subtitles, trim, fade)
- Mixing or modifying audio (amix, atempo, volume)
- Frame-accurate trimming (can only cut at keyframes)
- Burning subtitles
- Transcoding between codecs
- Compressing media
## Input/output seeking - detailed
[Reference](https://trac.ffmpeg.org/wiki/Seeking)
### Input seeking (-ss before -i)
```sh
ffmpeg -ss 00:00:03 -i input.mp4 -frames:v 1 output.jpg
```
Parses by keyframe - very fast but less accurate. In H264 with 25fps, keyframes typically every ~10 seconds. Resets timestamps to trimmed version - adjust filters accordingly.
### Output seeking (-ss after -i)
```sh
ffmpeg -i input.mp4 -ss 00:00:03 -frames:v 1 output.jpg
```
Decodes and discards until timestamp - frame-accurate but slower.
### The trimming problem
**Use output seeking WITHOUT `-c:v copy`** for reliable trimming:
1. **Input seeking + `-c:v copy` bug:** [FFmpeg bug report](https://trac.ffmpeg.org/ticket/8189), [Stack Overflow discussion](https://stackoverflow.com/questions/57450657/ffmpeg-fails-to-trim-beginning-of-clip)
2. **Output seeking + `-c:v copy` black frames:** Copies frames that depend on a missing keyframe. [Explanation](https://trac.ffmpeg.org/wiki/Seeking#codec-copy)
From [ffmpeg docs](https://ffmpeg.org/ffmpeg.html#Main-options):
> When used as an input option, seeks in this input file to position. In most formats it is not possible to seek exactly, so ffmpeg will seek to the closest seek point before position. When transcoding and -accurate_seek is enabled (the default), this extra segment between the seek point and position will be decoded and discarded. When doing stream copy or when -noaccurate_seek is used, it will be preserved.
Re-encoded output may have different bitrate - adjust as needed.
## GPU acceleration - detailed
### Nvidia (NVENC)
[Reference](https://trac.ffmpeg.org/wiki/HWAccelIntro#NVENC)
H264 via Nvidia GPU:
```sh
ffmpeg -i input.avi -c:v h264_nvenc output.mp4
```
H265 via Nvidia GPU:
```sh
ffmpeg -i input.avi -c:v hevc_nvenc output.mp4
```
### Intel Quick Sync Video (QSV)
[Reference](https://trac.ffmpeg.org/wiki/Hardware/QuickSync)
```sh
ffmpeg -init_hw_device qsv=hw -filter_hw_device hw -i input.avi -c:v h264_qsv output.mp4
```
### AMD (VAAPI)
[Reference](https://trac.ffmpeg.org/wiki/Hardware/VAAPI)
More complex setup and less broadly supported than Nvidia/Intel options.
```
### references/core-concepts.md
```markdown
# Core Concepts
Foundational concepts that apply across all ffmpeg tasks.
## -c copy (stream copy)
[Reference](https://ffmpeg.org/ffmpeg.html#Streamcopy)
Use `-c copy` whenever possible. It re-muxes video and audio without re-encoding, which is significantly faster.
- `-c copy` - copy all streams
- `-c:v copy` - copy video only
- `-c:a copy` - copy audio only (same as `-acodec copy`)
**Remuxing vs transcoding:** Remuxing rewraps streams into a new container without altering them. Transcoding changes compression and quality. MP4 can be remuxed to MKV and MOV because they are all H264 containers.
### When NOT to use -c copy
- Applying video filters (scale, overlay, subtitles, trim, fade) or modifying audio (amix, atempo, volume) - these require re-encoding
- Frame-accurate trimming - `-c copy` can only cut at keyframes, leading to rough/inaccurate edits
- Burning subtitles into video
- Transcoding between different codecs
- Compressing media
## Input/output seeking
[Seeking reference](https://trac.ffmpeg.org/wiki/Seeking)
### Input seeking (-ss before input)
```sh
ffmpeg -ss 00:00:03 -i input.mp4 -frames:v 1 output.jpg
```
Parses by keyframe - very fast but less accurate. In H264 with 25fps there is typically a keyframe every 10 seconds. Resets video timestamps to the trimmed version.
### Output seeking (-ss after input)
```sh
ffmpeg -i input.mp4 -ss 00:00:03 -frames:v 1 output.jpg
```
Decodes but discards input until the timestamp. Frame-accurate but slower because it must decode.
### Trimming advice
**Use output seeking WITHOUT `-c:v copy`** (re-encode the output) for reliable trimming:
- There is an [open bug](https://trac.ffmpeg.org/ticket/8189) with input seeking + `-c:v copy` ([discussion](https://stackoverflow.com/questions/57450657/ffmpeg-fails-to-trim-beginning-of-clip))
- Output seeking with `-c:v copy` can produce black frames due to copying frames that depend on a missing keyframe ([explanation](https://trac.ffmpeg.org/wiki/Seeking#codec-copy))
From [ffmpeg docs](https://ffmpeg.org/ffmpeg.html#Main-options):
> When used as an input option, seeks in this input file to position. In most formats it is not possible to seek exactly, so ffmpeg will seek to the closest seek point before position. When transcoding and -accurate_seek is enabled (the default), this extra segment between the seek point and position will be decoded and discarded. When doing stream copy or when -noaccurate_seek is used, it will be preserved.
The re-encoded output may have a different bitrate - adjust accordingly.
## Encoding quick reference
### Codec flags
| Flag | Purpose |
|---|---|
| `-c:v` | Video encoder |
| `-c:a` | Audio encoder |
| `-c:a aac` | AAC audio (default, good practice to specify) |
| `-c:a libmp3lame` | [MP3 encoding](https://trac.ffmpeg.org/wiki/Encode/MP3) |
| `-an` | Disable audio output |
### H264 (libx264)
[Reference](https://trac.ffmpeg.org/wiki/Encode/H.264)
Default for MP4 output. Always specify `-c:v libx264` explicitly.
- **CRF scale:** 0-51 (0 = lossless 8-bit, 23 = default, 51 = worst)
- **Recommended range:** 17-28, consider 17-18 visually lossless
- CRF +6 = roughly half bitrate; CRF -6 = roughly double bitrate
- Common high-quality: `-crf 18`, better quality: `-crf 10`
### H265 (libx265)
Similar controls to H264. Use `-vtag hvc1` for Apple device compatibility (iOS AirDrop, etc.):
```sh
ffmpeg -i input.mp4 -c:v libx265 -vtag hvc1 -c:a copy output.mp4
```
### VP9 (libvpx-vp9)
[Reference](https://trac.ffmpeg.org/wiki/Encode/VP9)
Open, royalty-free format used by YouTube. Saves 20-50% bitrate vs libx264 at same quality.
- **CRF range:** 0-63 (lower = better, 31 recommended for 1080p)
- **Constant quality:** must use `-crf` with `-b:v 0`
- Default audio encoder: libopus
```sh
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 15 -b:v 0 -c:a libopus output.webm
```
## format=yuv420p
[Reference](https://trac.ffmpeg.org/wiki/Encode/H.264#Encodingfordumbplayers)
H264 YUV planar colour format for playback compatibility in most players. Use when transforming images to video or when experiencing playback issues.
> You may need to use `-vf format=yuv420p` (or the alias `-pix_fmt yuv420p`) for your output to work in QuickTime and most other players. These players only support the YUV planar color space with 4:2:0 chroma subsampling for H.264 video.
## -movflags +faststart
[Reference](https://trac.ffmpeg.org/wiki/Encode/H.264#faststartforwebvideo)
Moves metadata to the front of the container for faster online playback. [YouTube recommends](https://support.google.com/youtube/answer/1722171) faststart for uploads.
```sh
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
```
Supported in MP4, M4A, and MOV. Works with both libx264 and libx265:
```sh
ffmpeg -i input.mp4 -c:v libx265 -c:a copy -movflags +faststart output.mp4
```
Verify faststart is applied with:
```sh
ffprobe -v trace -i output.mp4
```
Look for `type:'moov'` near the beginning of output.
## GPU acceleration overview
### Nvidia (NVENC)
[Reference](https://trac.ffmpeg.org/wiki/HWAccelIntro#NVENC)
```sh
# H264 via Nvidia GPU
ffmpeg -i input.avi -c:v h264_nvenc output.mp4
# H265 via Nvidia GPU
ffmpeg -i input.avi -c:v hevc_nvenc output.mp4
```
### Intel Quick Sync Video (QSV)
[Reference](https://trac.ffmpeg.org/wiki/Hardware/QuickSync)
```sh
ffmpeg -init_hw_device qsv=hw -filter_hw_device hw -i input.avi -c:v h264_qsv output.mp4
```
### AMD (VAAPI)
[Reference](https://trac.ffmpeg.org/wiki/Hardware/VAAPI)
More complex setup and less broadly supported than Nvidia/Intel options.
```
### references/audio-processing.md
```markdown
# Audio Processing
## Replace audio in video
```sh
ffmpeg -i video.mp4 -i new_audio.mp3 \
-map 0:v -map 1:a -shortest -c:v copy -c:a aac output.mp4
```
- `-map 0:v` - video from first input
- `-map 1:a` - audio from second input
- [`-shortest`](https://ffmpeg.org/ffmpeg.html#Advanced-options) - trims output to shortest stream duration. Remove to keep full video length (muted after audio ends).
- `-c:v copy` - no video re-encoding
## Extract audio from video
Encode MP4 to MP3:
```sh
ffmpeg -i input.mp4 output.mp3
```
Extract audio with downsample to 16kHz mono MP3, plus extract muted video:
```sh
ffmpeg -i input.mp4 \
-ar 16000 -ab 48k -codec:a libmp3lame -ac 1 audio_out.mp3 \
-map 0:v -c:v copy -an video_out.mp4
```
[Audio options reference](https://ffmpeg.org/ffmpeg.html#Audio-Options)
| Flag | Purpose |
|---|---|
| `-ar` | Sample rate (e.g. 16000 = 16kHz) |
| `-b:a` (or `-ab`) | Audio bitrate (e.g. 48k = 48kbit/s) |
| `-ac 1` | Mono (1 channel) |
Extract AAC audio without re-encoding:
```sh
ffmpeg -i input.mp4 -map 0:a:0 -acodec copy output.aac
```
## Mix audio in video
Mix video's existing audio with a new audio file at lower volume:
```sh
ffmpeg -i video.mp4 -i music.mp3 \
-filter_complex "[1:a]volume=0.2[a1];[0:a][a1]amix=inputs=2:duration=shortest" \
-shortest -map 0:v -c:v copy -c:a aac output.mp4
```
- [`volume=0.2`](https://trac.ffmpeg.org/wiki/AudioVolume) - lowers music volume to 20%
- [`amix=inputs=2`](https://ffmpeg.org/ffmpeg-filters.html#amix) - mixes two audio streams
- `duration=shortest` - output audio as short as shortest input
- `-shortest` - still required to control final video length
Without volume change, simplify the filter to:
```sh
-filter_complex "[0:a][1:a]amix=inputs=2:duration=shortest"
```
[AAC reference](https://trac.ffmpeg.org/wiki/Encode/AAC)
## Combine two MP3 tracks
Fade out first track, fade in second, concatenate:
```sh
ffmpeg -i track1.mp3 -i track2.mp3 \
-filter_complex "[0:a]afade=t=out:st=2:d=3[a0];[1:a]afade=t=in:st=0:d=3[a1];[a0][a1]concat=n=2:v=0:a=1" \
-c:a libmp3lame -q:a 2 output.mp3
```
[`afade`](https://ffmpeg.org/ffmpeg-filters.html#afade-1) parameters:
- `t=out` / `t=in` - fade type
- `st=2` - start time in seconds
- `d=3` - duration in seconds
[`concat=n=2:v=0:a=1`](https://ffmpeg.org/ffmpeg-filters.html#concat) - concatenate 2 audio streams, no video (`v=0`).
[`-q:a 2`](https://trac.ffmpeg.org/wiki/Encode/MP3) - high quality MP3 output (~170-210 kbit/s stereo).
## Audio crossfade
```sh
ffmpeg -i track1.mp3 -i track2.mp3 \
-filter_complex "[0:0][1:0]acrossfade=d=3:c1=exp:c2=qsin" \
-c:a libmp3lame -q:a 2 output.mp3
```
[`acrossfade=d=3:c1=exp:c2=qsin`](https://ffmpeg.org/ffmpeg-filters.html#acrossfade) - 3-second crossfade where first track fades out quickly (exponential) and second fades in slowly (quarter sine).
## Change audio format
MP3 to WAV pcm_s32le (unsigned 32-bit little-endian), mono, 48kHz:
```sh
ffmpeg -i input.mp3 -acodec pcm_s32le -ac 1 -ar 48000 output.wav
```
[Audio types reference](https://trac.ffmpeg.org/wiki/audio%20types)
## Merge and normalise audio
Merge audio from two MP4 files, mix to mono equally, normalise volume, downsample to 16kHz, encode as MP3 at 64kbit/s:
```sh
ffmpeg -i video1.mp4 -i video2.mp4 \
-filter_complex "[0:a][1:a]amix=inputs=2:duration=longest,pan=mono|c0=.5*c0+.5*c1,dynaudnorm" \
-ar 16000 -c:a libmp3lame -b:a 64k merged_audio.mp3
```
- `pan=mono|c0=.5*c0+.5*c1` - output channel blends 50% left + 50% right
- `dynaudnorm` - dynamic audio normalisation (smoothens loud/quiet parts)
- [Channel manipulation reference](https://trac.ffmpeg.org/wiki/AudioChannelManipulation)
```
### references/advanced-editing.md
```markdown
# Advanced Editing
## Change playback speed
Speed up 1.5x without distorting audio:
```sh
ffmpeg -i input.mp4 \
-filter_complex "[0:v]setpts=PTS/1.5[v];[0:a]atempo=1.5[a]" \
-map "[v]" -map "[a]" output.mp4
```
- [`setpts=PTS/1.5`](https://ffmpeg.org/ffmpeg-filters.html#setpts_002c-asetpts) - speeds up video by 1.5x
- `atempo=1.5` - speeds up audio playback while preserving pitch
## Change FPS
Change video frame rate without changing audio speed:
```sh
ffmpeg -i input.mp4 -filter:v fps=60 output.mp4
```
## Jump cuts
Remove segments and keep only specified time ranges:
```sh
ffmpeg -i input.mp4 \
-vf "select='between(t,0.0,5.7)+between(t,11.0,18.0)+between(t,19.0,20.0)',setpts=N/FRAME_RATE/TB" \
-af "aselect='between(t,0.0,5.7)+between(t,11.0,18.0)+between(t,19.0,20.0)',asetpts=N/SR/TB" \
output.mp4
```
Used for making clips shorter, silence removal, removing transitions.
[`setpts=N/FRAME_RATE/TB` and `asetpts=N/SR/TB`](https://ffmpeg.org/ffmpeg-filters.html#setpts_002c-asetpts) reset presentation timestamps:
- `N` - count of consumed frames/audio samples (from 0)
- `FRAME_RATE` / `SR` - video frame rate / audio sample rate
- `TB` - timebase of input timestamps
## Video cropping for social media
Crop 1080x720 video to 720x1080 vertical by cropping chunks at specific timeframes, upscaling 1.5x:
```sh
ffmpeg -i input.mp4 \
-vf "split=3[1][2][3]; \
[1]trim=0.0:4.5,setpts=PTS-STARTPTS,crop=min(in_w-300\,480):min(in_h-0\,720):300:0,scale=720:1080,setsar=1:1[1]; \
[2]trim=4.5:8.5,setpts=PTS-STARTPTS,crop=min(in_w-500\,480):min(in_h-0\,720):500:0,scale=720:1080,setsar=1:1[2]; \
[3]trim=8.5,setpts=PTS-STARTPTS,crop=min(in_w-400\,480):min(in_h-0\,720):400:0,scale=720:1080,setsar=1:1[3]; \
[1][2][3]concat=n=3:v=1" \
-c:v libx264 -c:a copy output.mp4
```
- [`split=3`](https://ffmpeg.org/ffmpeg-filters.html#split_002c-asplit) - splits input into 3 named streams
- [`trim=0.0:4.5`](https://ffmpeg.org/ffmpeg-filters.html#trim) - cuts to time range; omit end to go to video end
- `setpts=PTS-STARTPTS` - required after trim before concat to reset timestamps
- [`crop=width:height:x:y`](https://ffmpeg.org/ffmpeg-filters.html#crop) - the `min()` prevents cropping outside frame boundaries
With black padding for out-of-bounds crops:
```sh
ffmpeg -i input.mp4 \
-vf "split=3[1][2][3]; \
[1]trim=0.0:4.5,setpts=PTS-STARTPTS,crop=min(in_w-1200\,480):min(in_h-0\,720):1200:0,pad=480:720:(ow-iw)/2:(oh-ih)/2:color=black,scale=720:1080,setsar=1:1[1]; \
[2]trim=4.5:8.5,setpts=PTS-STARTPTS,crop=min(in_w-500\,480):min(in_h-0\,720):500:0,pad=480:720:(ow-iw)/2:(oh-ih)/2:color=black,scale=720:1080,setsar=1:1[2]; \
[3]trim=8.5,setpts=PTS-STARTPTS,crop=min(in_w-400\,480):min(in_h-0\,720):400:0,pad=480:720:(ow-iw)/2:(oh-ih)/2:color=black,scale=720:1080,setsar=1:1[3]; \
[1][2][3]concat=n=3:v=1" \
-c:v libx264 -c:a copy output.mp4
```
## Overlay text on video (drawtext)
Three text messages with timed fade-in and semi-transparent background:
```sh
ffmpeg -i input.mp4 \
-vf "drawtext=text='Get ready':x=50:y=100:fontsize=80:fontcolor=black:alpha='if(gte(t,1)*lte(t,3),(t-1)/2,1)':box=1:boxcolor=#[email protected]:boxborderw=7:enable='gte(t,1)', \
drawtext=text='Set':x=50:y=200:fontsize=80:fontcolor=black:alpha='if(gte(t,6)*lte(t,10),(t-6)/4,1)':box=1:boxcolor=#[email protected]:boxborderw=7:enable='gte(t,6)', \
drawtext=text='BOOM!':x=50:y=300:fontsize=80:fontcolor=black:alpha='if(gte(t,10)*lte(t,15),(t-10)/5,1)':box=1:boxcolor=#[email protected]:boxborderw=7:enable='gte(t,10)'" \
-c:v libx264 output.mp4
```
[drawtext reference](https://ffmpeg.org/ffmpeg-filters.html#drawtext-1)
| Parameter | Purpose |
|---|---|
| [`enable='gte(t,1)'`](https://ffmpeg.org/ffmpeg-filters.html#Timeline-editing) | Controls visibility - show from t=1s. `*` is AND operator |
| `alpha='if(gte(t,1)*lte(t,3),(t-1)/2,1)'` | Fade in from t=1 to t=3, then fully opaque |
| `box=1` | Draw background behind text |
| `boxborderw=7` | Box padding in pixels |
| [`boxcolor=#[email protected]`](https://ffmpeg.org/ffmpeg-utils.html#color-syntax) | Background colour at 60% opacity |
| `fontfile=path.ttf` | Custom font file |
### Text from file with custom font
```sh
ffmpeg -i input.mp4 \
-vf "drawtext=textfile=text.txt:fontfile=Poppins-Regular.ttf:x=50:y=100:fontsize=40:fontcolor=black:alpha='if(gte(t,1)*lte(t,5),t-1,1)':box=1:boxcolor=#[email protected]:boxborderw=7:enable='gte(t,1)'" \
-c:v libx264 output.mp4
```
ffmpeg does not download `textfile=` or `fontfile=` files - they must be local. Use `textfile` instead of inline text to avoid shell special character issues.
## Subtitles
### Burn subtitles with custom font and style
```sh
ffmpeg -i input.mp4 -ss 00:00 -to 00:40 \
-vf "subtitles=subs.srt:fontsdir=.:force_style='FontName=Poppins,FontSize=24,PrimaryColour=&HFFFFFF,OutlineColour=&H4066B66B,Outline=1,BorderStyle=3'" \
-c:v libx264 -c:a copy output.mp4
```
- Use `FontName` (not filename) - check by opening the font file
- Specify `fontsdir` for the directory containing the font
- Colours: `&HBBGGRR` or `&HAABBGGRR` (FF = fully transparent, 00 = opaque)
- `PrimaryColour` = font colour
- `OutlineColour` with `Outline=1,BorderStyle=3` creates a coloured background
- [Burn subtitles reference](https://trac.ffmpeg.org/wiki/HowToBurnSubtitlesIntoVideo)
- [Subtitles filter reference](https://ffmpeg.org/ffmpeg-filters.html#subtitles-1)
For heavily customised subtitles use ASS format. [ASS reference](https://hhsprings.bitbucket.io/docs/programming/examples/ffmpeg/subtitle/ass.html). For pixel-perfect effects, create opaque images and overlay them.
### Embed subtitles in MKV (no re-encoding)
```sh
ffmpeg -i input.mp4 -i subs.srt \
-c copy -c:s srt -disposition:s:0 default output.mkv
```
- `-c:s srt` - subtitle format
- [`-disposition:s:0 default`](https://ffmpeg.org/ffmpeg.html#Stream-specifiers-1) - set as default subtitle track
### Extract subtitles from MKV
```sh
ffmpeg -i input.mkv -map 0:s:0 subs.srt
```
## Combine media assets
### Overlay image/logo on video
```sh
ffmpeg -i video.mp4 -i logo.png \
-filter_complex "overlay=x=(main_w-overlay_w)/8:y=(main_h-overlay_h)/8:enable='gte(t,1)*lte(t,7)'" \
-c:v libx264 -c:a copy output.mp4
```
- `main_w`/`main_h` - main video dimensions
- `overlay_w`/`overlay_h` - overlay image dimensions
- `enable` controls visibility timing
### Overlay with controlled transparency
```sh
ffmpeg -i video.mp4 -i logo.png \
-filter_complex "[1:v]format=argb,geq='p(X,Y)':a='0.5*alpha(X,Y)'[v1]; \
[0:v][v1]overlay=x=(main_w-overlay_w)/8:y=(main_h-overlay_h)/8:enable='gte(t,1)*lte(t,7)'" \
-c:v libx264 -c:a copy output.mp4
```
- `format=argb` - converts to ARGB for alpha channel support
- [`geq='p(X,Y)'`](https://ffmpeg.org/ffmpeg-filters.html#geq) - preserves original pixel colours
- `a='0.5*alpha(X,Y)'` - 50% transparency
### Video on background image
```sh
ffmpeg -i video.mp4 -i background.png \
-filter_complex "[1:v][0:v]overlay=(W-w)/2:(H-h)/2" \
-c:v libx264 -c:a copy output.mp4
```
- `[1:v][0:v]` - background first, then video on top
- `W`/`H` = first stream (background) dimensions; `w`/`h` = second stream (video)
### Concat intro + main + outro with background music
```sh
ffmpeg -i intro.mp4 -i main.mp4 -i outro.mp4 -i bgm.mp3 \
-filter_complex " \
[0:v]fps=30,format=yuv420p,setsar=1[intro_v]; \
[1:v]scale=-2:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2:black,fps=30,format=yuv420p,setsar=1[main_v]; \
[2:v]fps=30,format=yuv420p,setsar=1[outro_v]; \
[0:a]aformat=sample_fmts=fltp:channel_layouts=stereo[intro_a]; \
[1:a]aformat=sample_fmts=fltp:channel_layouts=stereo[main_a]; \
[2:a]aformat=sample_fmts=fltp:channel_layouts=stereo[outro_a]; \
[intro_v][intro_a][main_v][main_a][outro_v][outro_a]concat=n=3:v=1:a=1[combined_video][combined_audio]; \
[3:a]volume=0.1,aformat=sample_fmts=fltp,afade=t=in:ss=0:d=1.5,afade=t=out:st=20:d=2[bgm_faded]; \
[combined_audio][bgm_faded]amix=inputs=2:duration=first:dropout_transition=2[final_audio]" \
-map "[combined_video]" -map "[final_audio]" \
-c:v libx264 -c:a aac -shortest output.mp4
```
- `duration=first` - output audio duration matches combined audio
- `dropout_transition=2` - fade out shorter audio to avoid abrupt cutoff
- [`aformat=sample_fmts=fltp`](https://ffmpeg.org/ffmpeg.html#Audio-Options) - 32-bit float planar format (common in ffmpeg)
### Stack two videos vertically
```sh
ffmpeg -i top.mp4 -i bottom.mp4 \
-filter_complex " \
[0:v]scale=720:-2:force_original_aspect_ratio=decrease,pad=720:640:(ow-iw)/2:(oh-ih)/2:black[top]; \
[1:v]scale=720:-2:force_original_aspect_ratio=decrease,pad=720:640:(ow-iw)/2:(oh-ih)/2:black[bottom]; \
[top][bottom]vstack=inputs=2:shortest=1[v]" \
-map "[v]" -map 1:a -c:v libx264 -c:a aac -shortest output.mp4
```
- [`shortest=1`](https://ffmpeg.org/ffmpeg-filters.html#Options-for-filters-with-several-inputs-_0028framesync_0029) in vstack - follow shortest video stream
- `-shortest` - match output to shortest of video/audio
```
### references/asset-generation.md
```markdown
# Asset Generation
## Image to video
Create 10-second video from looping image with audio and fade-in:
```sh
ffmpeg -loop 1 -t 10 -i image.png -i audio.mp3 \
-vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:-1:-1:color=black,setsar=1,fade=t=in:st=0:d=1,format=yuv420p" \
-c:v libx264 -c:a aac -shortest output.mp4
```
- `-loop 1` - infinitely loop input image
- `-t 10` - limit loop duration to 10 seconds
- [`fade=t=in:st=0:d=1`](https://ffmpeg.org/ffmpeg-filters.html#fade) - 1-second fade-in at start
- [Loop reference](https://video.stackexchange.com/questions/12905/repeat-loop-input-video-with-ffmpeg)
For faster processing, download the image locally first (ffmpeg re-downloads each frame from URLs).
## Slideshow with fade
5 seconds per image, fading between images:
```sh
ffmpeg -loop 1 -t 5 -i image1.png -loop 1 -t 5 -i image2.png -i audio.mp3 \
-filter_complex " \
[0:v]format=yuv420p,fade=t=in:st=0:d=0.5,setpts=PTS-STARTPTS[v0]; \
[1:v]format=yuv420p,fade=t=out:st=4.5:d=0.5,setpts=PTS-STARTPTS[v1]; \
[v0][v1]xfade=transition=fade:duration=0.5:offset=4.5,format=yuv420p[v]" \
-map "[v]" -map 2:a -c:v libx264 -c:a aac -shortest output.mp4
```
Output is 9.5 seconds (0.5s overlap during transition). First image fades in, last fades out.
- Streams flow: `[0:v]` filtered to `[v0]`, `[1:v]` to `[v1]`, then xfade to `[v]`
- [`xfade=transition=fade:duration=0.5:offset=4.5`](https://trac.ffmpeg.org/wiki/Xfade) - starts transition at 4.5s of first image, lasts 0.5s
## Ken Burns effect (zoompan)
Zoom in on first image centre, fade to second image which zooms out from left:
```sh
ffmpeg -loop 1 -i image1.png -loop 1 -i image2.png -i audio.mp3 \
-filter_complex " \
[0:v]scale=8000:-1,zoompan=z='zoom+0.005':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=100:s=1920x1080:fps=25,trim=duration=4,format=yuv420p,setpts=PTS-STARTPTS[v0]; \
[1:v]scale=8000:-1,zoompan=z='if(lte(zoom,1.0),1.5,max(zoom-0.005,1.005))':x=0:y='ih/2-(ih/zoom/2)':d=100:s=1920x1080:fps=25,trim=duration=4,format=yuv420p,setpts=PTS-STARTPTS[v1]; \
[v0][v1]xfade=transition=fade:duration=1:offset=3,format=yuv420p[v]" \
-map "[v]" -map 2:a -c:v libx264 -c:a aac -shortest output.mp4
```
Output is 7 seconds (4s + 4s - 1s fade overlap).
[zoompan reference](https://ffmpeg.org/ffmpeg-filters.html#zoompan)
**Zoom-in parameters:**
- `z='zoom+0.005'` - each frame adds 0.005 zoom
- `x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)'` - pan to centre
- `d=100:s=1920x1080:fps=25` - 100 frames at 25fps = 4 seconds
**Zoom-out parameters:**
- `z='if(lte(zoom,1.0),1.5,max(zoom-0.005,1.005))'` - start at 1.5x zoom, decrease by 0.005 per frame until 1.005x
- `x=0` - pan from left side
**Key details:**
- `scale=8000:-1` - upscale first to avoid [jitteriness bug](https://trac.ffmpeg.org/ticket/4298) with zoompan. Costs more compute but smooth result.
- `trim=duration=4` required instead of `-t` because zoompan's internal frame/fps control overrides `-t`
- [Ken Burns Effect](https://en.wikipedia.org/wiki/Ken_Burns_effect)
## Create GIFs
Looping GIF from video, auto-scaled to 320px height, every 2nd frame, 10x speed:
```sh
ffmpeg -i input.mp4 \
-vf "select='gt(trunc(t/2),trunc(prev_t/2))',setpts='PTS*0.1',scale=trunc(oh*a/2)*2:320:force_original_aspect_ratio=decrease,pad=trunc(oh*a/2)*2:320:-1:-1" \
-loop 0 -an output.gif
```
- `select='gt(trunc(t/2),trunc(prev_t/2))'` - take every 2nd frame
- `setpts='PTS*0.1'` - 10x playback speed
- `-loop 0` - infinite loop (default, can be omitted); `-loop 1` loops once
- `-an` - no audio
## Video compilation with fades
Split single video into segments with fade effects and concatenate:
```sh
ffmpeg -i input.mp4 \
-filter_complex " \
[0:v]trim=start=11:end=15,setpts=PTS-STARTPTS,fade=t=in:st=0:d=0.5,fade=t=out:st=3.5:d=0.5[v1]; \
[0:a]atrim=start=11:end=15,asetpts=PTS-STARTPTS,afade=t=in:st=0:d=0.5,afade=t=out:st=3.5:d=0.5[a1]; \
[0:v]trim=start=21:end=25,setpts=PTS-STARTPTS,fade=t=in:st=0:d=0.5,fade=t=out:st=3.5:d=0.5[v2]; \
[0:a]atrim=start=21:end=25,asetpts=PTS-STARTPTS,afade=t=in:st=0:d=0.5,afade=t=out:st=3.5:d=0.5[a2]; \
[v1][a1][v2][a2]concat=n=2:v=1:a=1[outv][outa]" \
-map "[outv]" -map "[outa]" -c:v libx264 -c:a aac output.mp4
```
- `trim=start=X:end=Y` - cut video to time range; `atrim` for audio
- `setpts=PTS-STARTPTS` - reset timestamps from 0
- `fade=t=in:st=0:d=0.5` / `fade=t=out:st=3.5:d=0.5` - fade effects
- `afade` - audio fade equivalent
- `concat=n=2:v=1:a=1` - combine 2 segments with video and audio
## Thumbnails
### Single frame at specific time
```sh
ffmpeg -i input.mp4 -ss 00:00:07 -frames:v 1 thumbnail.png
```
With quality control (JPEG, 2 = best, 31 = worst):
```sh
ffmpeg -i input.mp4 -ss 00:00:07 -frames:v 1 -q:v 2 thumbnail.jpg
```
### Multiple thumbnails at different times
```sh
ffmpeg -i input.mp4 \
-filter_complex "[0:v]split=2[first][second];[first]select='gte(t,5)'[thumb1];[second]select='gte(t,15)'[thumb2]" \
-map [thumb1] -frames:v 1 thumb_5s.png \
-map [thumb2] -frames:v 1 thumb_15s.png
```
`-frames:v 1` - output only 1 video frame.
### Scene change detection thumbnail
```sh
ffmpeg -i input.mp4 -vf "select='gt(scene,0.4)'" -frames:v 1 -q:v 2 scene_thumb.jpg
```
[`gt(scene,0.4)`](https://www.ffmpeg.org/ffmpeg-filters.html#select_002c-aselect) - sensitivity 0 to 1 (lower = more sensitive). Recommended: 0.3-0.5.
## Image thumbnail from input images
Overlay multiple images on a base image:
```sh
ffmpeg -i base.png -i overlay1.png -i overlay2.png \
-filter_complex " \
[1]scale=640:360,pad=648:368:4:4:black[overlay1]; \
[2]scale=640:360,pad=648:368:4:4:black[overlay2]; \
[0][overlay1]overlay=0:main_h-overlay_h[tmp1]; \
[tmp1][overlay2]overlay=main_w-overlay_w:main_h-overlay_h" \
-frames:v 1 thumbnail.png
```
## Storyboards
### Scene-based tile
```sh
ffmpeg -i input.mp4 \
-vf "select='gt(scene,0.4)',scale=640:480,tile=2x2" \
-frames:v 1 storyboard.jpg
```
[`tile=2x2`](https://www.ffmpeg.org/ffmpeg-filters.html#Examples-169) creates a 2x2 grid from detected scenes.
### Separate images per scene
```sh
ffmpeg -i input.mp4 \
-vf "select='gt(scene,0.4)'" \
-vsync 0 scene_%03d.jpg
```
[`-vsync 0`](https://www.ffmpeg.org/ffmpeg-filters.html#Examples-126) drops duplicate frames from the same scene.
Note: `-vsync` is deprecated in newer ffmpeg versions; use `-fps_mode` instead. [Reference](https://ffmpeg.org/ffmpeg.html#:~:text=%2Dfps_mode)
### Keyframe-based tile
```sh
ffmpeg -skip_frame nokey -i input.mp4 \
-vf 'scale=640:480,tile=4x4' \
-an -vsync 0 keyframes_%03d.png
```
`-skip_frame nokey` - skip all non-keyframes.
### Every Nth frame
```sh
ffmpeg -i input.mp4 \
-vf "select=not(mod(n\,10)),scale=640:480,tile=4x2" \
-vsync 0 tile_%03d.png
```
Every 10th frame in 4x2 tiles. Remove `,tile=4x2` for individual frame images.
```