📋 GIT Converter v2.7 - Feature Summary & Changes 🚀 Major New Features Added 🎬 Codec & Container Selection - AV1 vs HEVC encoding - Choose between next-gen AV1 or mature HEVC - MKV vs MP4 containers - Flexibility vs device compatibility - User-controlled output format - Full control over final file type ⚙️ Advanced Quality Control - Source Quality mode - Bypass quality changes unless required - CRF options - 16 (near-lossless), 18 (recommended), 20 (balanced) - Custom bitrate control - Exact bitrate specification for precise file sizes - Encoder-specific optimization - Different parameters for AV1 vs HEVC 🎮 GPU/Encoder Selection - Auto-detection - Intelligent hardware detection with benchmarking - Manual selection - Choose specific GPU/encoder: - NVIDIA NVENC (HEVC/AV1) - AMD AMF (HEVC/AV1) - Intel Quick Sync (HEVC/AV1) - CPU encoding (SVT-AV1/x265) - Custom encoder selection - Two-stage interface - Auto-detect first, then option to override 🎨 Enhanced Color Correction - 8 specialized presets: - 2000s DVD Restore - 90s Quality Restore - VHS Quality Restore - Anime Preservation - Pink skin tone restoration (Topaz AI fix) - Warm/Cool color boosts - Fixed filter parameters - Resolved unsharp filter matrix size issues 🔧 Technical Improvements 📦 Modular Architecture - Separated concerns into focused modules: - hardware.sh - GPU detection & encoder selection - codec.sh - Codec & container options - quality.sh - Quality modes & bitrate control - filters.sh - Resolution, FPS, color correction - encode.sh - FFmpeg execution & monitoring ⚡ Performance Optimizations - Hardware benchmarking - Tests encoder speed before selection - Timeout protection - Prevents hanging during encoder tests - Better error reporting - Shows SUCCESS/FAILED/NOT AVAILABLE status - Improved timing logic - Cross-platform compatible timing 🖥️ User Experience - Two-stage workflow - Auto-detect → confirm/override - Clear menu navigation - Numbered options with validation - Real-time feedback - Shows what's being tested/selected - Fixed input validation - Proper regex for multi-digit numbers 🐛 Bug Fixes - Fixed unsharp filter - Corrected matrix size requirements (odd numbers only) - Fixed hue parameter - Corrected eq filter syntax - Fixed encoder detection - Improved hardware detection logic - Fixed menu display - Resolved command substitution output capture issues 🎯 Key Benefits - Full user control over encoding parameters - Hardware optimization with automatic fallbacks - Professional quality restoration options - Modular design for easy maintenance - Cross-platform compatibility (Windows/Linux)
378 lines
14 KiB
Bash
378 lines
14 KiB
Bash
#!/bin/bash
|
||
# ===================================================================
|
||
# LT Converter v3.0 — Professional Edition (December 2025)
|
||
# Author: LeakTechnologies
|
||
# Fixed: Converted folder always created in script directory
|
||
# Fixed: Filter chaining, input validation, bitrate handling
|
||
# ===================================================================
|
||
|
||
# Force window size in Git Bash on Windows
|
||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
|
||
printf '\e[8;45;100t'
|
||
fi
|
||
|
||
# Get the directory where the script is located
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
||
# Always create Converted folder in the script's directory
|
||
OUT="$SCRIPT_DIR/Converted"
|
||
mkdir -p "$OUT"
|
||
|
||
# Detect optimal encoder at startup
|
||
optimal_encoder=$(detect_hardware)
|
||
|
||
# Hardware detection and encoder selection
|
||
detect_hardware() {
|
||
echo "Detecting hardware and optimal encoder..."
|
||
|
||
# Detect GPU type
|
||
gpu_type="none"
|
||
if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi >/dev/null 2>&1; then
|
||
gpu_type="nvidia"
|
||
echo " ✓ NVIDIA GPU detected"
|
||
elif lspci 2>/dev/null | grep -iq "amd\|radeon"; then
|
||
gpu_type="amd"
|
||
echo " ✓ AMD GPU detected"
|
||
elif lspci 2>/dev/null | grep -iq "intel.*vga\|intel.*display"; then
|
||
gpu_type="intel"
|
||
echo " ✓ Intel GPU detected"
|
||
else
|
||
echo " ⚠ No GPU detected, will use CPU encoding"
|
||
fi
|
||
|
||
# Test encoder availability and speed
|
||
local best_encoder=""
|
||
local best_time=999999
|
||
|
||
# Test candidates based on GPU type
|
||
local candidates=()
|
||
case $gpu_type in
|
||
nvidia) candidates=("hevc_nvenc" "av1_nvenc" "libsvtav1" "libx265") ;;
|
||
amd) candidates=("hevc_amf" "av1_amf" "libsvtav1" "libx265") ;;
|
||
intel) candidates=("hevc_qsv" "av1_qsv" "libsvtav1" "libx265") ;;
|
||
*) candidates=("libsvtav1" "libx265") ;;
|
||
esac
|
||
|
||
# Quick benchmark each encoder
|
||
for enc in "${candidates[@]}"; do
|
||
if ffmpeg -hide_banner -loglevel error -encoders | grep -q "$enc"; then
|
||
echo " Testing $enc..."
|
||
start_time=$(date +%s.%N)
|
||
if ffmpeg -hide_banner -loglevel error -y -f lavfi -i "testsrc=duration=2:size=320x240:rate=1" \
|
||
-c:v "$enc" -f null - >/dev/null 2>&1; then
|
||
end_time=$(date +%s.%N)
|
||
test_time=$(echo "$end_time - $start_time" | bc -l 2>/dev/null || echo "1")
|
||
echo " $enc: ${test_time}s"
|
||
if (( $(echo "$test_time < $best_time" | bc -l 2>/dev/null || echo "0") )); then
|
||
best_time=$test_time
|
||
best_encoder=$enc
|
||
fi
|
||
fi
|
||
fi
|
||
done
|
||
|
||
if [[ -n "$best_encoder" ]]; then
|
||
echo " ✓ Selected: $best_encoder (fastest encoder)"
|
||
echo "$best_encoder"
|
||
return 0
|
||
else
|
||
echo " ⚠ No working encoder found, defaulting to libx265"
|
||
echo "libx265"
|
||
return 0
|
||
fi
|
||
}
|
||
|
||
clear
|
||
cat << "EOF"
|
||
|
||
╔═══════════════════════════════════════════════════════════════════════════════╗
|
||
║ ║
|
||
║ LT Converter v3.0 (December 2025) ║
|
||
║ by LeakTechnologies ║
|
||
║ ║
|
||
║ High-quality batch conversion with hardware acceleration ║
|
||
║ • AV1 & H.265 support ║
|
||
║ • Smart upscaling (Source / 720p / 1080p / 1440p / 4K / 2X / 4X) ║
|
||
║ • Optional 60 fps smooth motion ║
|
||
║ • Color correction for Topaz AI videos ║
|
||
║ • Clean 8-bit encoding (no green lines) ║
|
||
║ • AAC stereo audio ║
|
||
║ • MKV or MP4 output ║
|
||
║ ║
|
||
╚═══════════════════════════════════════════════════════════════════════════════╝
|
||
|
||
EOF
|
||
|
||
# — Resolution —
|
||
echo " Choose your resolution:"
|
||
echo
|
||
echo " 1) Source file resolution (no upscale)"
|
||
echo " 2) 720p (1280×720)"
|
||
echo " 3) 1080p (1920×1080)"
|
||
echo " 4) 1440p (2560×1440)"
|
||
echo " 5) 4K (3840×2160)"
|
||
echo " 6) 2X Upscale"
|
||
echo " 7) 4X Upscale"
|
||
echo
|
||
|
||
while true; do
|
||
read -p " Enter 1–7 → " res
|
||
if [[ -n "$res" && "$res" =~ ^[1-7]$ ]]; then
|
||
break
|
||
else
|
||
echo " Invalid input. Please enter a number between 1 and 7."
|
||
fi
|
||
done
|
||
|
||
case $res in
|
||
1) scale="" ; res_name="Source" ;;
|
||
2) scale="1280:720" ; res_name="720p" ;;
|
||
3) scale="1920:1080" ; res_name="1080p" ;;
|
||
4) scale="2560:1440" ; res_name="1440p" ;;
|
||
5) scale="3840:2160" ; res_name="4K" ;;
|
||
6) scale="iw*2:ih*2" ; res_name="2X" ;;
|
||
7) scale="iw*4:ih*4" ; res_name="4X" ;;
|
||
esac
|
||
|
||
# — Scaling Algorithm (only if upscaling) —
|
||
if [[ -n "$scale" ]]; then
|
||
clear
|
||
cat << "EOF"
|
||
╔═══════════════════════════════════════════════════════════════╗
|
||
║ Choose Scaling Algorithm ║
|
||
╚═══════════════════════════════════════════════════════════════╝
|
||
|
||
1) Bicubic (fast, good quality)
|
||
2) Lanczos (best quality, slower)
|
||
3) Bilinear (fastest, basic quality)
|
||
EOF
|
||
echo
|
||
|
||
while true; do
|
||
read -p " Enter 1–3 → " scale_opt
|
||
if [[ -n "$scale_opt" && "$scale_opt" =~ ^[1-3]$ ]]; then
|
||
break
|
||
else
|
||
echo " Invalid input. Please enter a number between 1 and 3."
|
||
fi
|
||
done
|
||
|
||
case $scale_opt in
|
||
1) scale_flags="bicubic" ;;
|
||
2) scale_flags="lanczos" ;;
|
||
3) scale_flags="bilinear" ;;
|
||
esac
|
||
else
|
||
scale_flags="lanczos" # Default for consistency
|
||
fi
|
||
|
||
# — Encoding Options —
|
||
clear
|
||
cat << "EOF"
|
||
╔═════════════════════════════════════════════════════════════╗
|
||
║ Encoding Options ║
|
||
╚═══════════════════════════════════════════════════════════════╝
|
||
|
||
Auto-detected encoder: $optimal_encoder
|
||
|
||
1) Original FPS
|
||
2) 60 FPS
|
||
EOF
|
||
echo
|
||
|
||
while true; do
|
||
read -p " Enter 1–2 → " c
|
||
if [[ -n "$c" && "$c" =~ ^[1-2]$ ]]; then
|
||
break
|
||
else
|
||
echo " Invalid input. Please enter 1 or 2."
|
||
fi
|
||
done
|
||
|
||
# Set container and FPS
|
||
ext="mkv"
|
||
fps_filter=""
|
||
suf=""
|
||
if [[ "$c" == "2" ]]; then
|
||
fps_filter="fps=60"
|
||
suf="_60fps"
|
||
fi
|
||
|
||
# Use auto-detected encoder
|
||
codec="$optimal_encoder"
|
||
|
||
# — Color Correction Option —
|
||
clear
|
||
cat << "EOF"
|
||
╔═══════════════════════════════════════════════════════════════╗
|
||
║ Color Correction Option ║
|
||
╚═══════════════════════════════════════════════════════════════╝
|
||
|
||
1) No color correction
|
||
2) Restore pink skin tones (Topaz AI fix)
|
||
3) Warm color boost
|
||
4) Cool color boost
|
||
5) 2000s DVD Restore
|
||
6) 90s Quality Restore
|
||
7) VHS Quality Restore
|
||
8) Anime Preservation (clean lines & colors)
|
||
EOF
|
||
echo
|
||
|
||
while true; do
|
||
read -p " Enter 1–8 → " color_opt
|
||
if [[ -n "$color_opt" && "$color_opt" =~ ^[1-8]$ ]]; then
|
||
break
|
||
else
|
||
echo " Invalid input. Please enter a number between 1 and 8."
|
||
fi
|
||
done
|
||
|
||
case $color_opt in
|
||
1) color_filter=""; color_suf="" ;;
|
||
2) color_filter="eq=contrast=1.05:brightness=0.02:saturation=1.1:hue=-0.02"; color_suf="_colorfix" ;;
|
||
3) color_filter="eq=contrast=1.03:brightness=0.01:saturation=1.15:hue=-0.01"; color_suf="_warm" ;;
|
||
4) color_filter="eq=contrast=1.03:brightness=0.01:saturation=0.95:hue=0.02"; color_suf="_cool" ;;
|
||
5) color_filter="eq=contrast=1.08:brightness=0.03:saturation=1.2:hue=0:gamma=0.95,unsharp=4:4:0.8:4:4:0.0"; color_suf="_dvdrestore" ;;
|
||
6) color_filter="eq=contrast=1.12:brightness=0.05:saturation=1.3:hue=0:gamma=0.92,unsharp=5:5:1.0:5:5:0.0,hqdn3d=3:2:2:3"; color_suf="_90srestore" ;;
|
||
7) color_filter="eq=contrast=1.15:brightness=0.08:saturation=1.4:hue=0:gamma=0.90,unsharp=5:5:1.0:5:5:0.0,hqdn3d=3:2:2:3"; color_suf="_vhsrestore" ;;
|
||
8) color_filter="eq=contrast=1.02:brightness=0:saturation=1.05:hue=0:gamma=1.0,unsharp=3:3:0.5:3:3:0.0,gradfun=2.5:2.5"; color_suf="_anime" ;;
|
||
esac
|
||
|
||
# — Quality Selection —
|
||
clear
|
||
cat << "EOF"
|
||
╔═══════════════════════════════════════════════════════════════╗
|
||
║ Choose Quality Mode ║
|
||
╚═══════════════════════════════════════════════════════════════╝
|
||
|
||
1) High Quality AV1 (CRF 18) - Best compression
|
||
2) High Quality HEVC (CRF 18) - Fast encoding
|
||
3) Near-Lossless AV1 (CRF 16) - Maximum quality
|
||
4) Near-Lossless HEVC (CRF 16) - Quality/Speed balance
|
||
5) Custom bitrate
|
||
EOF
|
||
echo
|
||
|
||
while true; do
|
||
read -p " Enter 1–5 → " b
|
||
if [[ -n "$b" && "$b" =~ ^[1-5]$ ]]; then
|
||
break
|
||
else
|
||
echo " Invalid input. Please enter a number between 1 and 5."
|
||
fi
|
||
done
|
||
|
||
# Set quality parameters based on auto-detected encoder
|
||
case $b in
|
||
1)
|
||
if [[ "$codec" == *"av1"* ]]; then
|
||
quality_params="-crf 18 -preset 6"
|
||
quality_name="AV1 CRF 18"
|
||
else
|
||
quality_params="-crf 18 -quality 23"
|
||
quality_name="HEVC CRF 18"
|
||
fi
|
||
;;
|
||
2)
|
||
if [[ "$codec" == *"av1"* ]]; then
|
||
quality_params="-crf 16 -preset 4"
|
||
quality_name="AV1 CRF 16"
|
||
else
|
||
quality_params="-crf 16 -quality 28"
|
||
quality_name="HEVC CRF 16"
|
||
fi
|
||
;;
|
||
3)
|
||
echo
|
||
echo "Enter bitrate (e.g., 5000k, 8000k):"
|
||
read -p "→ " custom_bitrate
|
||
if [[ -n "$custom_bitrate" ]]; then
|
||
quality_params="-b:v $custom_bitrate"
|
||
quality_name="Custom $custom_bitrate"
|
||
else
|
||
quality_params="-crf 18 -quality 23"
|
||
quality_name="HEVC CRF 18"
|
||
fi
|
||
;;
|
||
esac
|
||
|
||
# Force 8-bit input with performance optimization
|
||
bitdepth_filter="-pix_fmt yuv420p -movflags +faststart"
|
||
|
||
echo
|
||
echo "Encoding → $codec | $res_name | $ext $suf${color_suf} @ $quality_name"
|
||
echo
|
||
|
||
# Process video files - handle drag-and-drop vs double-click
|
||
if [[ $# -gt 0 ]]; then
|
||
# Files were dragged onto the script
|
||
video_files=("$@")
|
||
echo "Processing dragged files: ${#video_files[@]} file(s)"
|
||
else
|
||
# Double-clicked - process all video files in current directory
|
||
shopt -s nullglob
|
||
video_files=(*.mp4 *.mkv *.mov *.avi *.wmv *.ts *.m2ts)
|
||
shopt -u nullglob
|
||
|
||
if [[ ${#video_files[@]} -eq 0 ]]; then
|
||
echo "No video files found in current directory."
|
||
read -p "Press Enter to exit"
|
||
exit 0
|
||
fi
|
||
echo "Processing all video files in current directory: ${#video_files[@]} file(s)"
|
||
fi
|
||
|
||
for f in "${video_files[@]}"; do
|
||
[[ -f "$f" ]] || continue
|
||
|
||
# Extract basename for output filename (handles both relative and absolute paths)
|
||
basename_f=$(basename "$f")
|
||
out="$OUT/${basename_f%.*}${suf}${color_suf}__cv.$ext"
|
||
[[ -f "$out" ]] && { echo "SKIP $f (already exists)"; continue; }
|
||
|
||
# Build filter chain
|
||
filter_chain=""
|
||
if [[ -n "$scale" ]]; then
|
||
filter_chain="scale=${scale}:flags=${scale_flags}"
|
||
fi
|
||
|
||
if [[ -n "$fps_filter" ]]; then
|
||
if [[ -n "$filter_chain" ]]; then
|
||
filter_chain="${filter_chain},${fps_filter}"
|
||
else
|
||
filter_chain="$fps_filter"
|
||
fi
|
||
fi
|
||
|
||
if [[ -n "$color_filter" ]]; then
|
||
if [[ -n "$filter_chain" ]]; then
|
||
filter_chain="${filter_chain},${color_filter}"
|
||
else
|
||
filter_chain="$color_filter"
|
||
fi
|
||
fi
|
||
|
||
echo "Processing: $f → $(basename "$out")"
|
||
|
||
# Build optimized ffmpeg command
|
||
if [[ -n "$filter_chain" ]]; then
|
||
ffmpeg -y -i "$f" -pix_fmt yuv420p -vf "$filter_chain" \
|
||
-c:v "$codec" $quality_params -c:a aac -b:a 192k -ac 2 "$out"
|
||
else
|
||
ffmpeg -y -i "$f" -pix_fmt yuv420p \
|
||
-c:v "$codec" $quality_params -c:a aac -b:a 192k -ac 2 "$out"
|
||
fi
|
||
|
||
if [[ $? -eq 0 ]]; then
|
||
echo "DONE → $(basename "$out")"
|
||
else
|
||
echo "ERROR → Failed to process $f"
|
||
fi
|
||
echo
|
||
done
|
||
|
||
echo "========================================================"
|
||
echo "All finished — files in '$OUT'"
|
||
echo "========================================================"
|
||
read -p "Press Enter to exit" |