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
findcommand with file type filtering- Pattern matching with
-inameflags - 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:
printfcreates a menu list- Piped to
fzffor interactive selection --layout=reverseputs the prompt at the top--headeradds a title- Result stored in
$operationvariable
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}"
fiFile 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
fzffor 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 secondframe_%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-clipImprovements You Could Make
- Add More Operations: Resize, rotate, add watermarks
- Batch Processing: Process multiple files at once
- Progress Bars: Show encoding progress
- Presets: Save common settings as presets
- Configuration File: Store default settings
- Error Handling: Better validation and error recovery
- 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}"
;;
esacTakeaways
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!