Last active
August 18, 2025 06:12
-
-
Save nickangtc/3053f30b054f5304f3a4517a449923cb to your computer and use it in GitHub Desktop.
Batch apply LUTs to video files using Bash and ffmpeg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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