Skip to content

Instantly share code, notes, and snippets.

@nickangtc
Last active August 18, 2025 06:12
Show Gist options
  • Save nickangtc/3053f30b054f5304f3a4517a449923cb to your computer and use it in GitHub Desktop.
Save nickangtc/3053f30b054f5304f3a4517a449923cb to your computer and use it in GitHub Desktop.
Batch apply LUTs to video files using Bash and ffmpeg
#!/bin/bash
# Simple LUT Batch Processing Script
# Usage: ./apply_lut_batch_simple.sh <input_folder> <output_folder> <lut_file>
set -e # Exit on error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
print_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
print_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
# Function to check prerequisites
check_prerequisites() {
print_status "Checking prerequisites..."
# Check if ffmpeg exists
if ! command -v ffmpeg &> /dev/null; then
print_error "ffmpeg not found. Please install ffmpeg first:"
print_error " brew install ffmpeg"
exit 1
fi
print_success "ffmpeg found"
# Check battery status
battery_info=$(pmset -g batt | grep -o '[0-9]*%' | head -1)
power_source=$(pmset -g batt | grep -o 'AC Power\|Battery Power')
print_status "Power status: $power_source ($battery_info)"
if [[ "$power_source" == "Battery Power" ]]; then
print_warning "Running on battery power. Video processing is CPU intensive."
print_warning "Consider plugging in your MacBook for overnight processing."
fi
}
# Function to validate arguments
validate_arguments() {
if [ $# -ne 3 ]; then
echo "Usage: $0 <input_folder> <output_folder> <lut_file>"
echo ""
echo "Example:"
echo " $0 ~/Desktop/input ~/Desktop/output ~/Desktop/my_lut.cube"
exit 1
fi
INPUT_FOLDER="$1"
OUTPUT_FOLDER="$2"
LUT_FILE="$3"
# Validate input folder
if [ ! -d "$INPUT_FOLDER" ]; then
print_error "Input folder does not exist: $INPUT_FOLDER"
exit 1
fi
# Validate LUT file
if [ ! -f "$LUT_FILE" ]; then
print_error "LUT file does not exist: $LUT_FILE"
exit 1
fi
if [[ ! "$LUT_FILE" =~ \.(cube|CUBE)$ ]]; then
print_error "LUT file must have .cube extension: $LUT_FILE"
exit 1
fi
# Create output folder if it doesn't exist
if [ ! -d "$OUTPUT_FOLDER" ]; then
print_status "Creating output folder: $OUTPUT_FOLDER"
mkdir -p "$OUTPUT_FOLDER"
fi
print_success "Arguments validated"
print_status "Input folder: $INPUT_FOLDER"
print_status "Output folder: $OUTPUT_FOLDER"
print_status "LUT file: $LUT_FILE"
}
# Function to get video files
get_video_files() {
# Find all video files in input folder
VIDEO_FILES=()
while IFS= read -r -d '' file; do
VIDEO_FILES+=("$file")
done < <(find "$INPUT_FOLDER" -maxdepth 1 -type f \( -iname "*.mp4" -o -iname "*.mov" \) -print0)
if [ ${#VIDEO_FILES[@]} -eq 0 ]; then
print_error "No video files found in input folder: $INPUT_FOLDER"
exit 1
fi
print_success "Found ${#VIDEO_FILES[@]} video files to process"
}
# Function to process a single video
process_video() {
local input_file="$1"
local filename=$(basename "$input_file")
local name_without_ext="${filename%.*}"
local extension="${filename##*.}"
local output_file="$OUTPUT_FOLDER/${name_without_ext}_LUT_applied.${extension}"
# Check if output already exists
if [ -f "$output_file" ]; then
print_warning "Output file already exists, skipping: $filename"
return 2
fi
# Record start time
local start_time=$(date +%s)
local start_timestamp=$(date '+%Y-%m-%d %H:%M:%S')
print_status "Processing: $filename"
echo "STARTED: $filename at $start_timestamp" >> "$LOG_FILE"
# Run FFmpeg with error handling
if ffmpeg -hide_banner -loglevel error \
-i "$input_file" \
-vf "lut3d='$LUT_FILE'" \
-c:v h264_videotoolbox \
-preset fast \
-c:a copy \
"$output_file" 2>> "$LOG_FILE"; then
# Record end time and calculate duration
local end_time=$(date +%s)
local end_timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local duration=$((end_time - start_time))
local hours=$((duration / 3600))
local minutes=$(((duration % 3600) / 60))
local seconds=$((duration % 60))
if [ $hours -gt 0 ]; then
local duration_str="${hours}h ${minutes}m ${seconds}s"
elif [ $minutes -gt 0 ]; then
local duration_str="${minutes}m ${seconds}s"
else
local duration_str="${seconds}s"
fi
print_success "Completed: $filename (took $duration_str)"
echo "COMPLETED: $filename at $end_timestamp (duration: $duration_str)" >> "$LOG_FILE"
return 0
else
local end_timestamp=$(date '+%Y-%m-%d %H:%M:%S')
print_error "Failed: $filename"
echo "FAILED: $filename at $end_timestamp" >> "$LOG_FILE"
return 1
fi
}
# Main processing function
main() {
# Set up variables
START_TIME=$(date +%s)
LOG_FILE="$OUTPUT_FOLDER/lut_processing_$(date +%Y%m%d_%H%M%S).log"
PROCESSED_COUNT=0
SUCCESS_COUNT=0
FAILED_COUNT=0
SKIPPED_COUNT=0
# Initialize log file
echo "LUT Batch Processing Log - Started at $(date)" > "$LOG_FILE"
echo "Input: $INPUT_FOLDER" >> "$LOG_FILE"
echo "Output: $OUTPUT_FOLDER" >> "$LOG_FILE"
echo "LUT: $LUT_FILE" >> "$LOG_FILE"
echo "----------------------------------------" >> "$LOG_FILE"
print_status "Starting LUT batch processing..."
print_status "Log file: $LOG_FILE"
print_warning "Remember to use Amphetamine or Caffeinate or similar to keep your Mac awake!"
# Process each video file
TOTAL_FILES=${#VIDEO_FILES[@]}
for i in "${!VIDEO_FILES[@]}"; do
local file="${VIDEO_FILES[$i]}"
local current=$((i + 1))
echo ""
print_status "Processing file $current of $TOTAL_FILES"
# Calculate estimated time remaining (after first file)
if [ $PROCESSED_COUNT -gt 0 ]; then
local elapsed=$(($(date +%s) - START_TIME))
local avg_time=$((elapsed / PROCESSED_COUNT))
local remaining_files=$((TOTAL_FILES - current + 1))
local estimated_remaining=$((avg_time * remaining_files))
local hours=$((estimated_remaining / 3600))
local minutes=$(((estimated_remaining % 3600) / 60))
print_status "Estimated time remaining: ${hours}h ${minutes}m"
fi
# Process the file
if process_video "$file"; then
((SUCCESS_COUNT++))
else
local exit_code=$?
if [ $exit_code -eq 2 ]; then
((SKIPPED_COUNT++))
else
((FAILED_COUNT++))
echo "Failed to process: $file" >> "$LOG_FILE"
fi
fi
((PROCESSED_COUNT++))
done
# Calculate total time
local total_time=$(($(date +%s) - START_TIME))
local hours=$((total_time / 3600))
local minutes=$(((total_time % 3600) / 60))
local seconds=$((total_time % 60))
echo ""
print_success "Processing completed!"
print_status "Total time: ${hours}h ${minutes}m ${seconds}s"
# Add timing summary to log file
echo "" >> "$LOG_FILE"
echo "========================================" >> "$LOG_FILE"
echo "PROCESSING SUMMARY" >> "$LOG_FILE"
echo "========================================" >> "$LOG_FILE"
echo "Total processing time: ${hours}h ${minutes}m ${seconds}s" >> "$LOG_FILE"
echo "Files succeeded: $SUCCESS_COUNT" >> "$LOG_FILE"
echo "Files failed: $FAILED_COUNT" >> "$LOG_FILE"
echo "Files skipped: $SKIPPED_COUNT" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
echo "Individual file timings:" >> "$LOG_FILE"
grep -E "^(STARTED|COMPLETED|FAILED):" "$LOG_FILE" | while IFS= read -r line; do
echo " $line" >> "$LOG_FILE.tmp"
done
if [ -f "$LOG_FILE.tmp" ]; then
cat "$LOG_FILE.tmp" >> "$LOG_FILE"
rm "$LOG_FILE.tmp"
fi
echo "========================================" >> "$LOG_FILE"
# Print final summary
echo ""
print_status "=== PROCESSING SUMMARY ==="
print_status "Total files found: $TOTAL_FILES"
print_status "Files processed: $PROCESSED_COUNT"
print_status "Files succeeded: $SUCCESS_COUNT"
print_status "Files failed: $FAILED_COUNT"
print_status "Files skipped: $SKIPPED_COUNT"
print_status "Log file: $LOG_FILE"
print_status "Check log file for detailed timing information!"
}
# Script entry point
echo "=== Simple LUT Batch Processing Script ==="
echo ""
# Run prerequisite checks
check_prerequisites
# Validate and parse arguments
validate_arguments "$@"
# Get list of video files
get_video_files
# Show confirmation
echo ""
print_status "Ready to process ${#VIDEO_FILES[@]} video files"
print_status "Estimated processing time: 2-5 minutes per video"
print_warning "Make sure to prevent sleep using Amphetamine or Caffeinate or similar!"
echo ""
read -p "Continue with processing? (y/N): " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_status "Processing cancelled"
exit 0
fi
# Run main processing
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment