FFmpeg and Automated Video Flows (Beginner Recipes)

FFmpeg and Automated Video Flows (Beginner Recipes)

Copy-paste recipes for converting, trimming, captioning, and batching with FFmpeg.

Copy‑paste first, then tweak. Everything here works on macOS (Terminal) and Windows (PowerShell).

What FFmpeg is & when to use it

FFmpeg is the Swiss‑Army knife of media. Paste a short command, get a precise, repeatable result: convert, trim, join, resize, caption, analyze. It’s perfect for creators who want consistent outputs without clicky menus.

Use it when you need:

If a command overwrites a file, you’ll see -y (yes to overwrite). Prefer safety? Remove -y and FFmpeg will ask first.


Installing & verifying

macOS (Homebrew)

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install ffmpeg
ffmpeg -version
ffprobe -version

Windows (Chocolatey | run PowerShell as Administrator)

Set-ExecutionPolicy Bypass -Scope Process -Force; `
[System.Net.ServicePointManager]::SecurityProtocol = `
[System.Net.ServicePointManager]::SecurityProtocol -bor 3072; `
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

choco install ffmpeg -y
ffmpeg -version
ffprobe -version

If ffmpeg isn’t found after installing, close and reopen your terminal so PATH updates. You’ll use ffprobe (ships with FFmpeg) to inspect files before editing.


Inspect before you edit (ffprobe)

Prevent avoidable re‑renders and wrong aspect ratios—peek first.

Quick look (human‑friendly)

ffprobe -hide_banner -i "input.mp4"

Targeted facts (easy to paste into notes)

# Resolution and frame rate
ffprobe -v error -select_streams v:0   -show_entries stream=width,height,r_frame_rate   -of default=noprint_wrappers=1 "input.mp4"

# Audio layout and sample rate
ffprobe -v error -select_streams a:0   -show_entries stream=channel_layout,sample_rate   -of default=noprint_wrappers=1 "input.mp4"

Why it matters:


The five starter recipes creators need

Assume H.264 + AAC outputs—friendly across platforms. Add -y to overwrite and -movflags +faststart for faster web playback.

1) Normalize audio loudness (dialog‑friendly)

Levels audio to a consistent perceived loudness so videos don’t jump in volume.

Simple one‑pass (great default)

ffmpeg -i input.wav -af "loudnorm=I=-16:TP=-1.5:LRA=11" -c:a aac -b:a 192k output.m4a

Works for embedded audio too:

ffmpeg -i input.mp4 -af "loudnorm=I=-16:TP=-1.5:LRA=11" -c:v copy -c:a aac -b:a 160k out.mp4

Tip: For music‑heavy mixes, try I=-14 LUFS. Save your “house” target in a notes file.


2) Resize & crop for platforms (16:9 → 9:16 Shorts, 1:1 square)

Horizontal → vertical (9:16, 1080×1920)

ffmpeg -i input.mp4 -vf "scale=-2:1920,crop=1080:1920"   -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p   -c:a aac -b:a 128k -movflags +faststart vertical_1080x1920.mp4

Square (1:1, 1080×1080)

ffmpeg -i input.mp4 -vf "scale=-2:1080,crop=1080:1080"   -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p   -c:a aac -b:a 128k -movflags +faststart square_1080.mp4

Letterbox instead of crop (keep full frame)

ffmpeg -i input.mp4   -vf "scale=1080:-2:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black"   -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p   -c:a aac -b:a 128k -movflags +faststart vertical_letterbox.mp4

Add -r 30 or -r 60 if you need a specific frame rate.


3) Burn subtitles from .srt (always‑visible captions)

Basic (default styling)

ffmpeg -i input.mp4 -vf "subtitles=subs.srt"   -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p   -c:a aac -b:a 160k -movflags +faststart captioned.mp4

Readable styling (font size & outline)

ffmpeg -i input.mp4   -vf "subtitles=subs.srt:force_style='Fontsize=26,OutlineColour=&H80000000&,BorderStyle=3,Outline=2,Shadow=0'"   -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p   -c:a aac -b:a 160k -movflags +faststart captioned_readable.mp4

If Windows quoting fights you, use double quotes and escape inner quotes.


4) Stitch intro + main + outro (concat demuxer)

Fast path (no re‑encode; codecs must match)

# Create a list file
printf "file 'intro.mp4'
file 'main.mp4'
file 'outro.mp4'
" > list.txt

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

Always‑works path (re‑encode to a house preset)

ffmpeg -f concat -safe 0 -i list.txt   -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p   -c:a aac -b:a 160k -movflags +faststart final_preset.mp4

5) Add a watermark/logo (safe corners & margins)

Bottom‑right (30px margin)

ffmpeg -i input.mp4 -i logo.png   -filter_complex "[1:v]scale=200:-1[wm];[0:v][wm]overlay=main_w-overlay_w-30:main_h-overlay_h-30"   -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p   -c:a aac -b:a 160k -movflags +faststart watermarked.mp4

Top‑left

ffmpeg -i input.mp4 -i logo.png   -filter_complex "[1:v]scale=200:-1[wm];[0:v][wm]overlay=30:30"   -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p   -c:a aac -b:a 160k -movflags +faststart watermarked_tl.mp4

Output presets (YouTube, Shorts, Podcast audio)

Adjust -crf (quality) and -preset (speed): lower CRF = higher quality; slower preset = smaller files, longer encodes.

YouTube 1080p (16:9)

ffmpeg -i input.mov   -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black"   -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p   -c:a aac -b:a 160k -movflags +faststart yt_1080p.mp4

YouTube 4K (16:9)

ffmpeg -i input.mov   -vf "scale=3840:2160:force_original_aspect_ratio=decrease,pad=3840:2160:(ow-iw)/2:(oh-ih)/2:black"   -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p   -c:a aac -b:a 192k -movflags +faststart yt_4k.mp4

Shorts / Reels (9:16 @ 30 fps)

ffmpeg -i input.mp4   -vf "scale=-2:1920,crop=1080:1920" -r 30   -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p   -c:a aac -b:a 128k -movflags +faststart short_1080x1920_30fps.mp4

Podcast / VO audio

# AAC .m4a with gentle loudness normalization
ffmpeg -i input.wav -af "loudnorm=I=-16:TP=-1.5:LRA=11" -c:a aac -b:a 192k podcast.m4a

# MP3 alternative
ffmpeg -i input.wav -af "loudnorm=I=-16:TP=-1.5:LRA=11" -c:a libmp3lame -b:a 192k podcast.mp3

Use yuv420p for the widest compatibility and always add -movflags +faststart for web playback.


Speeding up or keeping it simple

Pick one path to start.

CPU (libx264/libx265) — simplest & consistent

ffmpeg -i input.mp4 -c:v libx264 -crf 20 -preset medium -c:a aac -b:a 160k out.mp4

Mac GPU (VideoToolbox) — fast on Apple silicon

ffmpeg -i input.mp4   -c:v h264_videotoolbox -b:v 6M -maxrate 8M -bufsize 12M   -c:a aac -b:a 160k -movflags +faststart out_vtb.mp4

For 4K, try ~20–30 Mb/s; for 1080p, ~6–10 Mb/s.

NVIDIA GPU (NVENC) — Windows/Linux with NVIDIA

ffmpeg -i input.mp4   -c:v h264_nvenc -preset p5 -cq 23 -b:v 0   -c:a aac -b:a 160k -movflags +faststart out_nvenc.mp4

-cq controls quality (lower = better). Keep -b:v 0 for CQ mode.

Rule of thumb: If you care most about size/quality per file, stick to CPU/libx264. If speed matters most, try VideoToolbox (Mac) or NVENC (NVIDIA).


Organizing repeatable “render recipes”

Keep a small recipes.json and a tiny script to apply them—everyone on the team renders the same way.

{
  "youtube_1080p": {
    "vf": "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black",
    "vcodec": "libx264", "crf": 20, "preset": "medium",
    "acodec": "aac", "ab": "160k", "ext": "mp4"
  },
  "shorts_9x16_1080x1920": {
    "vf": "scale=-2:1920,crop=1080:1920",
    "vcodec": "libx264", "crf": 20, "preset": "medium",
    "acodec": "aac", "ab": "128k", "ext": "mp4"
  },
  "podcast_audio": {
    "af": "loudnorm=I=-16:TP=-1.5:LRA=11",
    "acodec": "aac", "ab": "192k", "ext": "m4a"
  }
}

Folder/naming for final renders

/renders
  /youtube_1080p/
  /shorts_9x16_1080x1920/
  /podcast_audio/

Name files like: Project_Scene01_v03_youtube_1080p.mp4 — the preset is baked into the filename.

Hook & format breakdown

“Drop folder → auto render” (beginner batch)

Start with a manual batch before using watchers.

macOS/Linux (bash)

for f in to_render/*.mp4; do
  ffmpeg -y -i "$f"     -vf "scale=-2:1920,crop=1080:1920"     -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p     -c:a aac -b:a 128k -movflags +faststart     "renders/shorts_9x16_1080x1920/$(basename "${f%.*}")_short.mp4"
done

Windows (PowerShell)

Get-ChildItem .	o_render\*.mp4 | ForEach-Object {
  $name = [System.IO.Path]::GetFileNameWithoutExtension($_.FullName)
  ffmpeg -y -i $_.FullName `
    -vf "scale=-2:1920,crop=1080:1920" `
    -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p `
    -c:a aac -b:a 128k -movflags +faststart `
    "renders\shorts_9x16_1080x1920\$name`_short.mp4"
}

Watchers can re‑trigger mid‑copy and chew CPU. Add them later (PowerShell FileSystemWatcher, fswatch/entr on macOS) once presets are stable.


Troubleshooting (greatest hits)

“Unknown encoder ‘libx264’”
You’re using a minimal build. Reinstall via Homebrew/Chocolatey.
Workarounds: on Mac use -c:v h264_videotoolbox; on NVIDIA use -c:v h264_nvenc.

“Unknown encoder ‘libfdk_aac’”
Use built‑in AAC: -c:a aac -b:a 160k (FDK isn’t in many builds).

Subtitles filter errors (fonts)
Install common fonts or force style:

-vf "subtitles=subs.srt:force_style='Fontsize=26,Outline=2,BorderStyle=3'"

Windows quoting can be tricky—switch to double quotes and escape inner quotes.

Concat fails / “Codec not found”
Stream‑copy concat requires matching streams. If in doubt, re‑encode:
-c:v libx264 -c:a aac with your preset.

Audio out of sync
For VFR → CFR: add -vf fps=30 (or your target).
Stubborn? -af aresample=async=1:first_pts=0.

“Invalid argument”
Most often a mis‑typed filter graph or quotes. Start minimal and add filters one by one.

Green video / odd colors
Ensure -pix_fmt yuv420p for widest compatibility.


FAQ & resources

CRF or bitrate?
CRF for CPU encoders (quality‑first). Bitrate/CQ for GPU or strict delivery specs.

Best preset?
Start with -preset medium. Faster → -preset fast. Smaller files (slower) → -preset slow.

Good CRF?
18–22 for 1080p H.264. Lower CRF = higher quality/larger files.

Bookmarks

Hook & format breakdown