FFmpeg CLI in Bash

1 · What is FFmpeg?

FFmpeg is a cross‑platform collection of libraries and a powerful command‑line tool (ffmpeg) for recording, converting, streaming and analysing audio & video content. It supports hundreds of formats and codecs, offers a rich filtergraph engine, and is script‑friendly — making it a cornerstone of multimedia automation in Bash workflows.

Latest stable release: 7.0.2 “Dijkstra” (Aug 03 2024) 

2 · Installing FFmpeg from Bash

2.1 · macOS (Homebrew)

brew install ffmpeg --with-sdl2 --with-fdk-aac --with-opencore-amr

2.2 · Ubuntu / Debian

sudo apt update
sudo apt install ffmpeg

2.3 · Windows (Chocolatey or MSYS‑bash)

choco install ffmpeg
# or download the full/static build from gyan.dev

Check your installation quickly with ffmpeg -version.

3 · Command Structure Essentials

The CLI follows a consistent pattern:

ffmpeg [global_options] -i input
       [input_options][output_options] …
       output

4 · Every‑Day Conversions

4.1 · Transcode video to H.264 (MP4)

ffmpeg -i input.mov -c:v libx264 -preset slow -crf 22 -c:a aac -b:a 192k output.mp4

4.2 · Extract audio only

ffmpeg -i video.mp4 -vn -c:a libmp3lame -qscale:a 2 podcast.mp3

4.3 · Lossless copy (container remux)

ffmpeg -i source.mkv -c copy target.mp4

4.4 · Batch convert a folder in Bash

for f in *.wav; do
    ffmpeg -i "$f" -c:a flac "${f%.wav}.flac"
done

Option -crf (0‑51) trades quality for size in constant‑rate‑factor encodes.

5 · Filtergraph Fundamentals

5.1 · Scaling & Padding

ffmpeg -i in.mp4 -vf "scale=1280:720:flags=lanczos,pad=1280:720:0:0:black" \
       -c:v libx264 -crf 20 out.mp4

5.2 · Overlay a logo

ffmpeg -i bg.mp4 -i logo.png -filter_complex \
       "[0:v][1:v] overlay=10:10,format=yuv420p" \
       -c:v libx264 -crf 23 -c:a copy branded.mp4

5.3 · Trim, fade & cross‑fade audio

ffmpeg -i song.mp3 -af "atrim=0:30,afade=t=out:st=25:d=5" \
       short_fade.mp3

Complex filtergraphs can be scripted via files: -filter_complex_script.

6 · Subtitles, Metadata & Chapters

7 · Screen Capture & Device Sources

7.1 · macOS screen with audio

ffmpeg -f avfoundation -framerate 60 -i "1:0" -c:v hevc_videotoolbox -b:v 8M \
       -c:a aac -pix_fmt yuv420p screen.mov

7.2 · Linux X11 desktop to GIF

ffmpeg -f x11grab -video_size 1366x768 -i :0.0 -t 5 -vf \
       "fps=10,scale=600:-1:flags=lanczos" capture.gif

8 · Network Streaming (RTMP / SRT / HLS)

8.1 · Push to YouTube RTMP

ffmpeg -re -i local.mp4 -c:v copy -c:a aac -b:a 128k \
       -f flv rtmp://a.rtmp.youtube.com/live2/YOUR_STREAM_KEY

8.2 · Low‑latency SRT send / receive

# Sender
ffmpeg -re -i talk.mp4 -c copy -f mpegts "srt://listener_ip:2025?pkt_size=1316"

# Receiver
ffmpeg -i "srt://0.0.0.0:2025?mode=listener" -c copy live.ts

8.3 · Generate an HLS multi‑bitrate set

ffmpeg -i master.mp4 -filter_complex \
  "[0:v]split=3[v1][v2][v3];
   [v1]scale=640:-2:flags=lanczos[v1out];
   [v2]scale=1280:-2:flags=lanczos[v2out];
   [v3]scale=1920:-2:flags=lanczos[v3out]" \
  -map "[v1out]" -c:v:0 libx264 -b:v:0 800k -maxrate:v:0 856k -bufsize:v:0 1200k \
  -map "[v2out]" -c:v:1 libx264 -b:v:1 1400k -maxrate:v:1 1498k -bufsize:v:1 2100k \
  -map "[v3out]" -c:v:2 libx264 -b:v:2 3000k -maxrate:v:2 3210k -bufsize:v:2 4500k \
  -map 0:a -c:a aac -b:a 128k -f hls -hls_time 6 -hls_playlist_type vod \
  -hls_segment_filename "v%v/file%03d.ts" -master_pl_name master.m3u8 \
  -var_stream_map "v:0,a:0 v:1,a:0 v:2,a:0"  v%v/prog.m3u8

9 · Hardware Acceleration (HW‑Accel)

PlatformEncoder flagNote
Intel Quick Sync-c:v h264_qsv Use -look_ahead 1 for VBR mode
NVIDIA NVENC-c:v h264_nvenc -preset p7 = best quality
Apple Silicon VTB-c:v hevc_videotoolbox Keep -pix_fmt yuv420p for compatibility

Combine -hwaccel for decoding with a HW encoder for full pipe offload.

Cropping Video & Images with FFmpeg

Precise framing using the crop filter

Basic Syntax

ffmpeg -i input.mp4 -vf "crop=<width>:<height>:<x>:<y>" \
       -c:v libx264 -crf 22 -c:a copy output.mp4

width and height define the new frame size, while x and y set the top-left offset. Arithmetic expressions are accepted (e.g. in_w/2, in_h-100).

Center-Crop to Square

ffmpeg -i clip.mp4 -vf "crop=min(in_w\,in_h):min(in_w\,in_h)" \
       -c:v libx264 -crf 20 -c:a copy square.mp4

Vertical 9:16 Slice (for Reels/Shorts)

ffmpeg -i landscape.mp4 -vf "crop=ih*9/16:ih:(iw-ih*9/16)/2:0" \
       -c:v libx264 -crf 21 -c:a copy vertical.mp4

Batch Crop Folder (Bash Loop)

for vid in *.mp4; do
    ffmpeg -i "$vid" -vf "crop=1280:720:0:0" -c:v libx265 -crf 28 \
           -c:a copy "cropped_$vid"
done

Cropping All JPGs in a Folder Using Bash + FFmpeg

A recursive script for cropping multiple JPEG files

Command

find . -type f -iname "*.jpg" -exec sh -c '
  for img; do
    out="${img%.*}_cropped.jpg"
    ffmpeg -y -i "$img" -vf "crop=300:300:0:0" "$out"
  done
' sh {} +

Explanation

  1. find . -type f -iname "*.jpg": Recursively finds all .jpg files, case-insensitive, from the current directory downward.
  2. -exec sh -c '…' sh {} +: Executes a subshell that loops over the found files as arguments.
  3. for img; do …; done: Iterates over all matched JPEG paths.
  4. out="${img%.*}_cropped.jpg": Constructs the output filename by removing the extension and appending _cropped.jpg.
  5. ffmpeg -y -i "$img" -vf "crop=300:300:0:0" "$out": Applies a 300x300 top-left crop and writes to the new file.

You can adjust the crop=width:height:x:y values to suit your framing. This script preserves the original image and outputs side-by-side copies.

10 · Bash Automation Patterns

10.1 · Parallel encode with GNU parallel

parallel -j4 'ffmpeg -i {} -c:v libx265 -crf 28 {.}.hevc.mp4' ::: *.mp4

10.2 · Create a daily timelapse

# capture one JPEG every minute
*/1 * * * * ffmpeg -y -f v4l2 -i /dev/video0 -vframes 1 ~/timelapse/%Y%m%d_%H%M.jpg

# assemble at midnight:
0 0 * * * ffmpeg -r 30 -pattern_type glob -i "~/timelapse/$(date +\%Y\%m\%d)_*.jpg" \
                 -c:v libx264 -pix_fmt yuv420p "timelapse_$(date +\%Y\%m\%d).mp4"

10.3 · Monitoring with Bash functions

check_and_fix(){
  local f="$1"
  if ffprobe -v error "$f" &>/dev/null; then
      echo "✓ $f looks good"
  else
      echo "✗ $f broken ⇒ re‑encode"
      ffmpeg -i "$f" -c:v libx264 -c:a aac -movflags +faststart "fixed_$f"
  fi
}
for vid in *.mp4; do check_and_fix "$vid"; done

11 · Debugging and Best Practices

12 · Further Reading & Resources