Back to Blog

Building a Powerful FFmpeg CLI Tool: A Bash Script Deep Dive

The Problem

Working with video and audio files often requires multiple FFmpeg commands, each with different flags and parameters. Typing these commands repeatedly is tedious, error-prone, and breaks workflow. I needed a single, interactive tool that could handle common media operations with a clean interface.

The Solution

I built a bash script that combines FFmpeg's power with fzf (fuzzy finder) for an intuitive, menu-driven experience. Here's what it does and how it works.

Concepts Used

Before diving into the code, let's break down the key concepts this script demonstrates:

1. Shell Scripting Fundamentals

  • Variable declarations and color codes
  • Conditional statements (if, case)
  • Input/output redirection (>&2)
  • Command substitution

2. fzf (Fuzzy Finder)

  • Interactive menu selection
  • Custom prompts and headers
  • Reverse layout for better UX
  • Integration with shell pipelines

3. File Operations

  • find command with file type filtering
  • Pattern matching with -iname flags
  • Directory depth limiting (-maxdepth)
  • String manipulation with sed

4. FFmpeg Operations

  • Format conversion
  • Video filters (-vf)
  • Codec selection (-c:v, -c:a)
  • Time-based clipping (-ss, -to)
  • Frame extraction

5. User Interaction

  • Interactive prompts with read
  • Error handling and validation
  • Color-coded output for feedback
  • Directory creation for outputs

6. ANSI Color Codes

  • Terminal text coloring
  • Status indicators (green=success, red=error, yellow=warning)
  • Reset codes for proper formatting

The Script Breakdown

#!/bin/bash
 
# Color definitions for terminal output
GREEN="\033[0;32m"
RED="\033[0;31m"
YELLOW="\033[1;33m"
RESET="\033[0m"
PURPLE="\e[35m"

Why colors? Visual feedback makes the tool more user-friendly. Green for success, red for errors, yellow for warnings.

echo -e "${GREEN}Welcome to FFMPEG CLI Tool.${RESET}" >&2
echo "" >&2

>&2: Redirects output to stderr. This ensures messages appear even when output is piped.

operation=$(printf "1. Format Conversion\n2. Convert to BNW\n3. Extract Audio\n4. Clip Media\n5. Extract frames" | fzf --prompt="Choose the operation to peform" --layout=reverse --header="Choose what you want to do:")

fzf Integration:

  • printf creates a menu list
  • Piped to fzf for interactive selection
  • --layout=reverse puts the prompt at the top
  • --header adds a title
  • Result stored in $operation variable
if [[ -n "$operation" ]]; then
  echo -e "${GREEN}Select a media file from the current directory:${RESET}"
  media_file=$(find . -maxdepth 1 -type f \( -iname "*.mp4" -o -iname "*.mkv" -o -iname "*.avi" -o -iname "*.webm" \) | sed 's|^\./||' | fzf --prompt="File: " --header="Pick a video file to process:")
else
  echo -e "${RED}No option selected. Aborting...!${RESET}"
fi

File Selection:

  • find . -maxdepth 1: Search only current directory
  • -type f: Files only (not directories)
  • \( -iname "*.mp4" -o ... \): Multiple file extensions (case-insensitive)
  • sed 's|^\./||': Remove ./ prefix for cleaner display
  • Piped to fzf for interactive file selection
case "$operation" in
"1. Convert to BNW")
  ffmpeg -i $media_file -vf format=gray -c:v libx264 -preset ultrafast -crf 28 -c:a copy bnw_$media_file.mp4
  ;;

Case Statement: Handles different operations. This one converts to black & white:

  • -vf format=gray: Video filter for grayscale
  • -c:v libx264: H.264 video codec
  • -preset ultrafast: Encoding speed (faster = larger file)
  • -crf 28: Quality setting (higher = lower quality, smaller file)
  • -c:a copy: Copy audio without re-encoding
"3. Extract Audio")
  ffmpeg -i "$media_file" -q:a 0 -map a "${media_file%.*}.mp3"
  ;;

Audio Extraction:

  • -q:a 0: Highest audio quality
  • -map a: Map only audio stream
  • "${media_file%.*}": Remove file extension, add .mp3
"5. Extract Frames")
  mkdir -p frames
  ffmpeg -i "$media_file" -vf "fps=1" frames/frame_%04d.png
  echo -e "${GREEN}Frames extracted to ./frames directory${RESET}"
  ;;

Frame Extraction:

  • mkdir -p frames: Create directory (won't error if exists)
  • -vf "fps=1": Extract 1 frame per second
  • frame_%04d.png: Sequential naming (frame_0001.png, frame_0002.png, etc.)
"4. Clip Media")
  read -p "Start time (e.g., 00:00:15): " start
  read -p "Duration (e.g., 00:00:17): " end
  read -p "Enter name of output file: " output
  ffmpeg -hide_banner -loglevel error -y -ss "$start" -to "$end" -i "$media_file" -c:v libx264 -c:a aac "${output}.mp4"
  echo -e "${GREEN}Clipped video saved as $output${RESET}"
  ;;

Video Clipping:

  • read -p: Interactive prompt for user input
  • -hide_banner -loglevel error: Suppress FFmpeg output (only errors)
  • -y: Overwrite output file without asking
  • -ss "$start": Start time
  • -to "$end": End time
  • -c:a aac: AAC audio codec

Key Features

1. Interactive Menus

No need to remember command syntax. Just select from a menu.

2. Fuzzy File Finding

Type part of a filename to quickly find it. No need to type full paths.

3. Error Prevention

  • Validates operation selection
  • Checks file existence (implicitly via find)
  • Provides clear error messages

4. Flexible Input

  • Works with multiple video formats
  • Handles spaces in filenames (quotes)
  • Supports relative paths

5. User Feedback

  • Color-coded messages
  • Clear success/error indicators
  • Progress information

Usage Example

$ ./ffmpeg-tool.sh
Welcome to FFMPEG CLI Tool.
 
# fzf menu appears
Choose what you want to do:
> 4. Clip Media
  3. Extract Audio
  5. Extract Frames
  ...
 
# After selection, file picker appears
Pick a video file to process:
> video.mp4
  another-video.mkv
  ...
 
# For clipping, prompts appear
Start time (e.g., 00:00:15): 00:01:30
Duration (e.g., 00:00:17): 00:00:30
Enter name of output file: my-clip
 
# Success message
Clipped video saved as my-clip

Improvements You Could Make

  1. Add More Operations: Resize, rotate, add watermarks
  2. Batch Processing: Process multiple files at once
  3. Progress Bars: Show encoding progress
  4. Presets: Save common settings as presets
  5. Configuration File: Store default settings
  6. Error Handling: Better validation and error recovery
  7. Preview: Show video preview before processing

The Complete Script

Here's the full script for reference:

#!/bin/bash
 
GREEN="\033[0;32m"
RED="\033[0;31m"
YELLOW="\033[1;33m"
RESET="\033[0m"
PURPLE="\e[35m"
 
echo -e "${GREEN}Welcome to FFMPEG CLI Tool.${RESET}" >&2
echo "" >&2
 
operation=$(printf "1. Format Conversion\n2. Convert to BNW\n3. Extract Audio\n4. Clip Media\n5. Extract frames" | fzf --prompt="Choose the operation to peform" --layout=reverse --header="Choose what you want to do:")
 
if [[ -n "$operation" ]]; then
  echo -e "${GREEN}Select a media file from the current directory:${RESET}"
  media_file=$(find . -maxdepth 1 -type f \( -iname "*.mp4" -o -iname "*.mkv" -o -iname "*.avi" -o -iname "*.webm" \) | sed 's|^\./||' |
    fzf --prompt="File: " --header="Pick a video file to process:")
 
else
  echo -e "${RED}No option selected. Aborting...!${RESET}"
fi
 
case "$operation" in
"1. Convert to BNW")
  ffmpeg -i $media_file -vf format=gray -c:v libx264 -preset ultrafast -crf 28 -c:a copy bnw_$media_file.mp4
  ;;
"3. Extract Audio")
  ffmpeg -i "$media_file" -q:a 0 -map a "${media_file%.*}.mp3"
  ;;
"5. Extract Frames")
  mkdir -p frames
  ffmpeg -i "$media_file" -vf "fps=1" frames/frame_%04d.png
  echo -e "${GREEN}Frames extracted to ./frames directory${RESET}"
  ;;
"4. Clip Media")
  read -p "Start time (e.g., 00:00:15): " start
  read -p "Duration (e.g., 00:00:17): " end
  read -p "Enter name of output file: " output
  ffmpeg -hide_banner -loglevel error -y -ss "$start" -to "$end" -i "$media_file" -c:v libx264 -c:a aac "${output}.mp4"
  echo -e "${GREEN}Clipped video saved as $output${RESET}"
  ;;
esac

Takeaways

This script demonstrates how simple tools can create powerful workflows:

  • fzf makes CLI tools user-friendly
  • Bash scripting automates repetitive tasks
  • FFmpeg handles complex media operations
  • Color coding improves user experience

By combining these concepts, we've created a tool that's both powerful and accessible—proving that you don't need a GUI to have a great user experience.


Want to extend this script or have questions? Check out the FFmpeg documentation and fzf GitHub repo for more ideas!