#!/bin/bash # FastCompress v1.0 - macOS Edition # Interactive video compression tool using FFmpeg # Author: Faiz Intifada # Platform: macOS 10.13+ # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' WHITE='\033[1;37m' GRAY='\033[0;37m' DARKGRAY='\033[1;30m' NC='\033[0m' # No Color # Global variables SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FFMPEG_PATH="" SELECTED_FILE="" SEARCH_TERM="" # Supported formats SUPPORTED_FORMATS=("mp4" "mkv" "avi" "mov" "webm") #============================================================================= # Helper Functions #============================================================================= print_header() { clear echo "" echo -e "${CYAN}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${CYAN}║ ║${NC}" echo -e "${CYAN}║ ${WHITE}FastCompress v1.0${CYAN} ║${NC}" echo -e "${CYAN}║ ${GRAY}Video Compression Made Easy${CYAN} ║${NC}" echo -e "${CYAN}║ ║${NC}" echo -e "${CYAN}║ ${BLUE}by Faiz Intifada${CYAN} ║${NC}" echo -e "${CYAN}║ ║${NC}" echo -e "${CYAN}╚═══════════════════════════════════════════════════════╝${NC}" echo "" } check_ffmpeg() { # Check if ffmpeg is in PATH if command -v ffmpeg &> /dev/null; then FFMPEG_PATH=$(command -v ffmpeg) return 0 fi # Check in script directory if [ -f "$SCRIPT_DIR/ffmpeg" ]; then FFMPEG_PATH="$SCRIPT_DIR/ffmpeg" chmod +x "$FFMPEG_PATH" 2>/dev/null return 0 fi return 1 } show_ffmpeg_install_menu() { echo "" echo -e "${RED}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${RED}║ FFmpeg Required ║${NC}" echo -e "${RED}╚═══════════════════════════════════════════════════════╝${NC}" echo "" echo -e "${YELLOW}FastCompress needs FFmpeg to compress videos.${NC}" echo "" echo -e "${CYAN}Options:${NC}" echo -e " ${WHITE}1.${NC} ${GREEN}Install via Homebrew${NC} ${GRAY}(recommended)${NC}" echo "" echo -e " ${WHITE}2.${NC} ${YELLOW}Auto-download FFmpeg${NC} ${GRAY}(~70 MB)${NC}" echo "" echo -e " ${WHITE}3.${NC} ${YELLOW}View manual installation instructions${NC}" echo "" echo -e " ${WHITE}4.${NC} ${RED}Exit${NC}" echo "" while true; do echo -ne "${CYAN}Select option (1-4): ${NC}" read choice case $choice in 1|2|3|4) echo "$choice"; return ;; *) echo -e "${RED}Invalid selection. Please enter 1-4.${NC}" ;; esac done } install_ffmpeg_homebrew() { echo "" echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ Installing FFmpeg via Homebrew ║${NC}" echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}" echo "" # Check if Homebrew is installed if ! command -v brew &> /dev/null; then echo -e "${RED}✗ Homebrew not found!${NC}" echo "" echo -e "${YELLOW}Installing Homebrew first...${NC}" echo -e "${GRAY}This requires your password and may take a few minutes.${NC}" echo "" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" if [ $? -ne 0 ]; then echo "" echo -e "${RED}✗ Homebrew installation failed.${NC}" return 1 fi fi echo -e "${YELLOW}[1/2] Installing FFmpeg via Homebrew...${NC}" echo -e "${GRAY}This may take a few minutes...${NC}" echo "" brew install ffmpeg if [ $? -eq 0 ]; then echo "" echo -e "${GREEN}✓ FFmpeg installed successfully!${NC}" FFMPEG_PATH=$(command -v ffmpeg) return 0 else echo "" echo -e "${RED}✗ Installation failed.${NC}" return 1 fi } install_ffmpeg_download() { echo "" echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ Downloading FFmpeg ║${NC}" echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}" echo "" FFMPEG_URL="https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip" TEMP_ZIP="/tmp/ffmpeg_temp.zip" TARGET_PATH="$SCRIPT_DIR/ffmpeg" echo -e "${YELLOW}[1/3] Downloading FFmpeg...${NC}" echo -e "${GRAY}Source: evermeet.cx${NC}" echo -e "${GRAY}Size: ~70 MB${NC}" echo "" echo -e "${CYAN}⏳ Downloading... Please wait${NC}" curl -L "$FFMPEG_URL" -o "$TEMP_ZIP" 2>/dev/null if [ $? -ne 0 ]; then echo -e "${RED}✗ Download failed!${NC}" echo "" echo -e "${YELLOW}Please check your internet connection.${NC}" return 1 fi echo -e "${GREEN}✓ Download completed!${NC}" echo "" echo -e "${YELLOW}[2/3] Extracting FFmpeg...${NC}" unzip -q "$TEMP_ZIP" -d "/tmp/ffmpeg_extract" 2>/dev/null if [ $? -ne 0 ]; then echo -e "${RED}✗ Extraction failed!${NC}" rm -f "$TEMP_ZIP" return 1 fi # Find and copy ffmpeg binary FFMPEG_BIN=$(find /tmp/ffmpeg_extract -name "ffmpeg" -type f | head -n 1) if [ -z "$FFMPEG_BIN" ]; then echo -e "${RED}✗ FFmpeg binary not found in archive!${NC}" rm -rf "$TEMP_ZIP" /tmp/ffmpeg_extract return 1 fi cp "$FFMPEG_BIN" "$TARGET_PATH" chmod +x "$TARGET_PATH" echo -e "${GREEN}✓ Extraction completed!${NC}" echo "" echo -e "${YELLOW}[3/3] Verifying installation...${NC}" if [ -x "$TARGET_PATH" ]; then VERSION=$("$TARGET_PATH" -version 2>&1 | head -n 1) echo -e "${GREEN}✓ FFmpeg is working correctly!${NC}" echo -e "${GRAY}$VERSION${NC}" FFMPEG_PATH="$TARGET_PATH" else echo -e "${YELLOW}⚠ Verification failed, but file exists${NC}" FFMPEG_PATH="$TARGET_PATH" fi # Cleanup rm -rf "$TEMP_ZIP" /tmp/ffmpeg_extract echo "" echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ Installation Successful! ║${NC}" echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}" echo "" return 0 } show_manual_install_instructions() { echo "" echo -e "${CYAN}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${CYAN}║ Manual FFmpeg Installation ║${NC}" echo -e "${CYAN}╚═══════════════════════════════════════════════════════╝${NC}" echo "" echo -e "${YELLOW}Option A: Homebrew (Recommended)${NC}" echo -e "${GRAY} 1. Install Homebrew from:${NC}" echo -e "${WHITE} https://brew.sh${NC}" echo "" echo -e "${GRAY} 2. Run:${NC}" echo -e "${WHITE} brew install ffmpeg${NC}" echo "" echo -e "${YELLOW}Option B: Binary Download${NC}" echo -e "${GRAY} 1. Download FFmpeg from:${NC}" echo -e "${WHITE} https://evermeet.cx/ffmpeg/${NC}" echo "" echo -e "${GRAY} 2. Extract and copy ${WHITE}ffmpeg${GRAY} to this folder:${NC}" echo -e "${CYAN} $SCRIPT_DIR${NC}" echo "" } scan_video_files() { local files=() for ext in "${SUPPORTED_FORMATS[@]}"; do while IFS= read -r -d '' file; do files+=("$file") done < <(find "$SCRIPT_DIR" -maxdepth 1 -type f -iname "*.$ext" -print0 2>/dev/null) done # Sort files IFS=$'\n' files=($(sort <<<"${files[*]}")) unset IFS echo "${files[@]}" } format_file_size() { local bytes=$1 local mb=$(echo "scale=2; $bytes / 1048576" | bc) echo "$mb" } show_file_selection_menu() { local files=("$@") local count=${#files[@]} if [ $count -eq 0 ]; then return 1 fi if [ $count -eq 1 ]; then local filename=$(basename "${files[0]}") local size=$(stat -f%z "${files[0]}" 2>/dev/null || stat -c%s "${files[0]}" 2>/dev/null) local size_mb=$(format_file_size $size) echo "" echo -e "${YELLOW}[3/5] Video file detected${NC}" echo "" echo -e "${GRAY} File: ${WHITE}$filename${NC}" echo -e "${GRAY} Size: ${CYAN}$size_mb MB${NC}" echo "" echo -ne "${YELLOW}Proceed with this file? (y/n): ${NC}" read response if [[ "$response" =~ ^[Yy]$ ]]; then SELECTED_FILE="${files[0]}" return 0 else return 1 fi fi # Multiple files - show numbered menu while true; do print_header echo -e "${YELLOW}[3/5] Select video to compress${NC}" echo -e "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${NC}" echo -e "${CYAN}║ ${GRAY}Enter number to select, or type to search${CYAN} ║${NC}" echo -e "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${NC}" echo "" # Filter files based on search term local filtered_files=() if [ -z "$SEARCH_TERM" ]; then filtered_files=("${files[@]}") else for file in "${files[@]}"; do local basename=$(basename "$file") if [[ "$basename" == *"$SEARCH_TERM"* ]]; then filtered_files+=("$file") fi done fi # Show search bar if [ -n "$SEARCH_TERM" ]; then echo -e "${YELLOW}Search: ${WHITE}$SEARCH_TERM█${CYAN} [${#filtered_files[@]} of ${#files[@]} files]${NC}" else echo -e "${DARKGRAY}Type to search...${NC}" fi echo "" # Display files if [ ${#filtered_files[@]} -eq 0 ]; then echo -e "${YELLOW} No files match your search.${NC}" echo "" echo -ne "${YELLOW}Press Enter to clear search: ${NC}" read SEARCH_TERM="" continue fi local index=1 for file in "${filtered_files[@]}"; do local filename=$(basename "$file") local size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null) local size_mb=$(format_file_size $size) local ext="${filename##*.}" printf " ${WHITE}%2d.${NC} ${GRAY}%-45s ${CYAN}%10s MB ${DARKGRAY}[%s]${NC}\n" \ "$index" "$filename" "$size_mb" "${ext^^}" ((index++)) done echo "" echo -ne "${CYAN}Enter number (1-${#filtered_files[@]}) or search term: ${NC}" read input # Check if input is a number if [[ "$input" =~ ^[0-9]+$ ]]; then if [ "$input" -ge 1 ] && [ "$input" -le ${#filtered_files[@]} ]; then SELECTED_FILE="${filtered_files[$((input-1))]}" SEARCH_TERM="" return 0 else echo -e "${RED}Invalid number. Press Enter to continue...${NC}" read fi else # Use as search term SEARCH_TERM="$input" fi done } show_compression_menu() { echo "" echo -e "${YELLOW}[4/5] Select compression quality${NC}" echo "" echo -e "${CYAN}╔════════════════════════════════════════════════╗${NC}" echo -e "${CYAN}║ VIDEO COMPRESSION OPTIONS ║${NC}" echo -e "${CYAN}╚════════════════════════════════════════════════╝${NC}" echo "" echo -e " ${WHITE}1.${NC} ${GREEN}High Quality${NC} ${GRAY}(CRF 23, ~80% original size)${NC}" echo -e " ${DARKGRAY}└─ Preset: medium, Audio: 192k${NC}" echo "" echo -e " ${WHITE}2.${NC} ${YELLOW}Medium Quality${NC} ${GRAY}(CRF 28, ~60% original size)${NC}" echo -e " ${DARKGRAY}└─ Preset: medium, Audio: 128k${NC}" echo "" echo -e " ${WHITE}3.${NC} ${MAGENTA}Low Quality${NC} ${GRAY}(CRF 32, ~40% original size)${NC}" echo -e " ${DARKGRAY}└─ Preset: fast, Audio: 96k${NC}" echo "" echo -e " ${WHITE}4.${NC} ${CYAN}Custom Settings${NC}" echo -e " ${DARKGRAY}└─ Manual CRF and preset configuration${NC}" echo "" while true; do echo -ne "${CYAN}Select option (1-4): ${NC}" read choice case $choice in 1|2|3|4) echo "$choice"; return ;; *) echo -e "${RED}Invalid selection. Please enter 1-4.${NC}" ;; esac done } get_compression_parameters() { local choice=$1 case $choice in 1) echo "23 medium 192k High Quality" ;; 2) echo "28 medium 128k Medium Quality" ;; 3) echo "32 fast 96k Low Quality" ;; 4) echo "" echo -e "${CYAN}[CUSTOM SETTINGS]${NC}" echo "" # Get CRF while true; do echo -ne "${YELLOW}Enter CRF value (0-51, lower = better quality): ${NC}" read crf if [[ "$crf" =~ ^[0-9]+$ ]] && [ "$crf" -ge 0 ] && [ "$crf" -le 51 ]; then break else echo -e "${RED}Invalid CRF value. Please enter 0-51.${NC}" fi done # Get preset echo "" echo -e "${YELLOW}Available presets:${NC}" echo -e "${GRAY} 1. ultrafast (fastest, larger file)${NC}" echo -e "${GRAY} 2. fast${NC}" echo -e "${GRAY} 3. medium (balanced)${NC}" echo -e "${GRAY} 4. slow (slower, better compression)${NC}" echo "" while true; do echo -ne "${YELLOW}Select preset (1-4): ${NC}" read preset_choice case $preset_choice in 1) preset="ultrafast"; break ;; 2) preset="fast"; break ;; 3) preset="medium"; break ;; 4) preset="slow"; break ;; *) echo -e "${RED}Invalid selection.${NC}" ;; esac done # Calculate audio bitrate if [ "$crf" -le 25 ]; then audio="192k" elif [ "$crf" -le 30 ]; then audio="128k" else audio="96k" fi echo "$crf $preset $audio Custom (CRF $crf, $preset)" ;; esac } compress_video() { local input_file=$1 local crf=$2 local preset=$3 local audio_bitrate=$4 local quality_name=$5 local filename=$(basename "$input_file") local dirname=$(dirname "$input_file") local basename="${filename%.*}" local extension="${filename##*.}" local output_file="$dirname/${basename}_compressed.$extension" # Check if output exists if [ -f "$output_file" ]; then echo "" echo -e "${YELLOW}[WARNING] Output file already exists:${NC}" echo -e "${GRAY} $output_file${NC}" echo "" echo -ne "${YELLOW}Overwrite existing file? (y/n): ${NC}" read response if [[ ! "$response" =~ ^[Yy]$ ]]; then echo "" echo -e "${YELLOW}Operation cancelled.${NC}" return 1 fi fi echo "" echo -e "${YELLOW}[5/5] Confirmation${NC}" echo "" echo -e "${DARKGRAY}═══════════════════════════════════════════════════════${NC}" echo -e "${GRAY} Input File: ${WHITE}$filename${NC}" echo -e "${GRAY} Output File: ${WHITE}${basename}_compressed.$extension${NC}" echo -e "${GRAY} Quality: ${CYAN}$quality_name${NC}" echo -e "${GRAY} Settings: ${GRAY}CRF $crf, Preset $preset, Audio $audio_bitrate${NC}" echo -e "${DARKGRAY}═══════════════════════════════════════════════════════${NC}" echo "" echo -ne "${YELLOW}Proceed with compression? (y/n): ${NC}" read confirm if [[ ! "$confirm" =~ ^[Yy]$ ]]; then echo "" echo -e "${YELLOW}Operation cancelled.${NC}" return 1 fi echo "" echo -e "${GREEN}╔════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ STARTING COMPRESSION ║${NC}" echo -e "${GREEN}╚════════════════════════════════════════════════╝${NC}" echo "" echo -e "${GRAY}Quality: ${CYAN}$quality_name${NC}" echo -e "${GRAY}Input: ${WHITE}$input_file${NC}" echo -e "${GRAY}Output: ${WHITE}$output_file${NC}" echo "" local start_time=$(date +%s) # Run FFmpeg with progress "$FFMPEG_PATH" -i "$input_file" -vcodec libx264 -crf "$crf" -preset "$preset" \ -acodec aac -b:a "$audio_bitrate" -y "$output_file" 2>&1 | \ grep --line-buffered "time=" | sed -u 's/.*time=\([^ ]*\).*/\1/' | \ while read time; do echo -ne "\r${CYAN}⏳ Compressing... Time: $time${NC}" done local ffmpeg_status=${PIPESTATUS[0]} local end_time=$(date +%s) local duration=$((end_time - start_time)) echo "" # New line after progress if [ $ffmpeg_status -eq 0 ]; then echo -e "${GREEN}✓ Compression completed successfully!${NC}" # Get file sizes local input_size=$(stat -f%z "$input_file" 2>/dev/null || stat -c%s "$input_file" 2>/dev/null) local output_size=$(stat -f%z "$output_file" 2>/dev/null || stat -c%s "$output_file" 2>/dev/null) local input_mb=$(format_file_size $input_size) local output_mb=$(format_file_size $output_size) local reduction=$(echo "scale=1; (1 - $output_size / $input_size) * 100" | bc) echo "" echo -e "${CYAN}File Size Comparison:${NC}" echo -e "${GRAY} Original: ${WHITE}$input_mb MB${NC}" echo -e "${GRAY} Compressed: ${WHITE}$output_mb MB${NC}" echo -e "${GRAY} Reduction: ${GREEN}$reduction%${NC}" echo -e "${GRAY} Time Taken: ${WHITE}$duration seconds${NC}" echo "" echo -e "${GRAY}Output saved to: ${CYAN}$output_file${NC}" return 0 else echo "" echo -e "${RED}✗ Compression failed!${NC}" echo -e "${YELLOW}Check FFmpeg errors above for details.${NC}" return 1 fi } #============================================================================= # Main Script #============================================================================= main() { print_header # Check FFmpeg echo -e "${YELLOW}[1/5] Checking FFmpeg availability...${NC}" if ! check_ffmpeg; then echo -e "${RED} ✗ FFmpeg not found!${NC}" choice=$(show_ffmpeg_install_menu) case $choice in 1) if ! install_ffmpeg_homebrew; then echo "" echo -e "${RED}Installation failed. Please try another method.${NC}" echo "" echo "Press Enter to exit..." read exit 1 fi ;; 2) if ! install_ffmpeg_download; then echo "" echo -e "${RED}Installation failed. Please try manual installation.${NC}" echo "" echo "Press Enter to exit..." read exit 1 fi ;; 3) show_manual_install_instructions echo -e "${YELLOW}After installing FFmpeg, please run this script again.${NC}" echo "" echo "Press Enter to exit..." read exit 0 ;; 4) echo "" echo -e "${YELLOW}Exiting...${NC}" exit 0 ;; esac echo "Press Enter to continue..." read print_header echo -e "${YELLOW}[1/5] Checking FFmpeg availability...${NC}" echo -e "${GREEN} ✓ FFmpeg installed successfully!${NC}" else echo -e "${GREEN} ✓ FFmpeg found: $FFMPEG_PATH${NC}" fi # Scan for video files echo "" echo -e "${YELLOW}[2/5] Scanning for video files...${NC}" echo -e "${GRAY} • Scanning: ${WHITE}$SCRIPT_DIR${NC}" video_files=($(scan_video_files)) if [ ${#video_files[@]} -eq 0 ]; then echo "" echo -e "${RED}[ERROR] No video files found in current folder!${NC}" echo "" echo -e "${YELLOW}Supported formats: ${WHITE}${SUPPORTED_FORMATS[*]}${NC}" echo "" echo -e "${GRAY}Please navigate to a folder containing video files and run the script again.${NC}" echo "" echo "Press Enter to exit..." read exit 1 fi echo -e "${GREEN} ✓ Found ${CYAN}${#video_files[@]}${GREEN} video file(s)${NC}" # Select video file if ! show_file_selection_menu "${video_files[@]}"; then echo "" echo -e "${YELLOW}Operation cancelled.${NC}" echo "" echo "Press Enter to exit..." read exit 0 fi local filename=$(basename "$SELECTED_FILE") local size=$(stat -f%z "$SELECTED_FILE" 2>/dev/null || stat -c%s "$SELECTED_FILE" 2>/dev/null) local size_mb=$(format_file_size $size) echo "" echo -e "${GREEN} ✓ Selected: ${WHITE}$filename ${GRAY}($size_mb MB)${NC}" # Show compression menu choice=$(show_compression_menu) # Get compression parameters params=$(get_compression_parameters $choice) read crf preset audio_bitrate quality_name <<< "$params" # Compress video if compress_video "$SELECTED_FILE" "$crf" "$preset" "$audio_bitrate" "$quality_name"; then echo "" echo -e "${CYAN}═══════════════════════════════════════════════════════${NC}" echo "" echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}║ COMPRESSION COMPLETED! ║${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}" echo "" echo -e "${GREEN}✓ Your compressed video is ready to use!${NC}" else echo "" echo -e "${CYAN}═══════════════════════════════════════════════════════${NC}" echo "" echo -e "${RED}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${RED}║ ║${NC}" echo -e "${RED}║ COMPRESSION FAILED ║${NC}" echo -e "${RED}║ ║${NC}" echo -e "${RED}╚═══════════════════════════════════════════════════════╝${NC}" echo "" echo -e "${RED}✗ Video compression failed. Check errors above.${NC}" fi echo "" echo -e "${DARKGRAY}─────────────────────────────────────────────────────────${NC}" echo -e "${DARKGRAY}FastCompress v1.0 | by Faiz Intifada${NC}" echo "" echo "Press Enter to exit..." read } # Run main function main