#!/bin/bash ################################################################################ # ENCODERPRO SCRIPT - PHASE 1 ################################################################################ # Purpose: Remove subtitle streams from movie files and re-encode safely # Features: # - Recursive directory scanning # - Safe file replacement using staging # - Original file archiving # - Restart-safe operation # - Detailed logging ################################################################################ set -euo pipefail # Exit on error, undefined vars, pipe failures ################################################################################ # CONFIGURATION ################################################################################ # Directories (CUSTOMIZE THESE) MOVIES_DIR="${MOVIES_DIR:-/mnt/user/movies}" ARCHIVE_DIR="${ARCHIVE_DIR:-/mnt/user/archive/movies}" WORK_DIR="${WORK_DIR:-/mnt/user/temp/encoderpro-work}" LOG_FILE="${LOG_FILE:-/var/log/encoderpro-movies.log}" # Encoding settings VIDEO_CODEC="${VIDEO_CODEC:-libx265}" VIDEO_PRESET="${VIDEO_PRESET:-medium}" VIDEO_CRF="${VIDEO_CRF:-23}" # File extensions to process FILE_EXTENSIONS=("mkv" "mp4" "avi" "m4v") # Dry run mode (set to 1 to test without encoding) DRY_RUN="${DRY_RUN:-0}" ################################################################################ # LOGGING FUNCTIONS ################################################################################ log() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] $*" | tee -a "$LOG_FILE" } log_error() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] ERROR: $*" | tee -a "$LOG_FILE" >&2 } log_success() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] SUCCESS: $*" | tee -a "$LOG_FILE" } ################################################################################ # VALIDATION FUNCTIONS ################################################################################ validate_dependencies() { local missing_deps=0 for cmd in ffmpeg ffprobe; do if ! command -v "$cmd" &> /dev/null; then log_error "Required command '$cmd' not found" missing_deps=1 fi done if [[ $missing_deps -eq 1 ]]; then log_error "Missing dependencies. Please install ffmpeg." exit 1 fi log "All dependencies satisfied" } validate_directories() { if [[ ! -d "$MOVIES_DIR" ]]; then log_error "Movies directory does not exist: $MOVIES_DIR" exit 1 fi # Create necessary directories mkdir -p "$ARCHIVE_DIR" "$WORK_DIR" # Ensure log directory exists local log_dir=$(dirname "$LOG_FILE") mkdir -p "$log_dir" log "Directory validation complete" log " Movies: $MOVIES_DIR" log " Archive: $ARCHIVE_DIR" log " Work: $WORK_DIR" log " Log: $LOG_FILE" } ################################################################################ # FILE PROCESSING FUNCTIONS ################################################################################ build_file_list() { local pattern="" for ext in "${FILE_EXTENSIONS[@]}"; do pattern="${pattern} -o -name *.${ext}" done pattern="${pattern# -o }" # Remove leading " -o " find "$MOVIES_DIR" -type f \( $pattern \) | sort } get_relative_path() { local file="$1" echo "${file#$MOVIES_DIR/}" } create_archive_path() { local relative_path="$1" echo "$ARCHIVE_DIR/$relative_path" } create_work_path() { local relative_path="$1" local filename=$(basename "$relative_path") local work_subdir="$WORK_DIR/$(dirname "$relative_path")" mkdir -p "$work_subdir" echo "$work_subdir/$filename" } reencode_file() { local input_file="$1" local output_file="$2" log "Encoding: $input_file" log " Output: $output_file" log " Codec: $VIDEO_CODEC, Preset: $VIDEO_PRESET, CRF: $VIDEO_CRF" # FFmpeg command: # -i: input file # -map 0:v: map all video streams # -map 0:a: map all audio streams # -c:v: video codec # -preset: encoding preset # -crf: quality setting (lower = better) # -c:a copy: copy audio without re-encoding # -sn: strip all subtitle streams # -movflags +faststart: optimize for streaming (mp4) # -n: never overwrite output file ffmpeg -hide_banner -loglevel warning -stats \ -i "$input_file" \ -map 0:v -map 0:a \ -c:v "$VIDEO_CODEC" \ -preset "$VIDEO_PRESET" \ -crf "$VIDEO_CRF" \ -c:a copy \ -sn \ -movflags +faststart \ -n \ "$output_file" 2>&1 | tee -a "$LOG_FILE" return ${PIPESTATUS[0]} } verify_output() { local output_file="$1" if [[ ! -f "$output_file" ]]; then log_error "Output file does not exist: $output_file" return 1 fi local filesize=$(stat -c%s "$output_file" 2>/dev/null || stat -f%z "$output_file" 2>/dev/null) if [[ $filesize -lt 1024 ]]; then log_error "Output file is suspiciously small: $output_file ($filesize bytes)" return 1 fi # Verify with ffprobe if ! ffprobe -v error "$output_file" &>/dev/null; then log_error "Output file failed ffprobe validation: $output_file" return 1 fi log "Output file verified: $output_file ($filesize bytes)" return 0 } process_file() { local input_file="$1" local relative_path=$(get_relative_path "$input_file") local archive_path=$(create_archive_path "$relative_path") local work_path=$(create_work_path "$relative_path") log "==========================================" log "Processing: $relative_path" if [[ $DRY_RUN -eq 1 ]]; then log "[DRY RUN] Would encode: $input_file -> $work_path" log "[DRY RUN] Would archive: $input_file -> $archive_path" log "[DRY RUN] Would move: $work_path -> $input_file" return 0 fi # Step 1: Re-encode to work directory if ! reencode_file "$input_file" "$work_path"; then log_error "Encoding failed for: $input_file" rm -f "$work_path" return 1 fi # Step 2: Verify output if ! verify_output "$work_path"; then log_error "Verification failed for: $work_path" rm -f "$work_path" return 1 fi # Step 3: Archive original local archive_dir=$(dirname "$archive_path") mkdir -p "$archive_dir" if ! mv "$input_file" "$archive_path"; then log_error "Failed to archive original: $input_file" rm -f "$work_path" return 1 fi log "Archived original: $archive_path" # Step 4: Move new file into place if ! mv "$work_path" "$input_file"; then log_error "Failed to move encoded file into place: $work_path -> $input_file" log_error "Original is in archive: $archive_path" return 1 fi log_success "Completed: $relative_path" return 0 } ################################################################################ # MAIN EXECUTION ################################################################################ cleanup() { log "Script interrupted or completed" } trap cleanup EXIT main() { log "==========================================" log "ENCODERPRO - PHASE 1" log "==========================================" log "Started at: $(date)" if [[ $DRY_RUN -eq 1 ]]; then log "DRY RUN MODE ENABLED" fi # Validate environment validate_dependencies validate_directories # Build file list log "Scanning for media files..." local file_count=0 local success_count=0 local failure_count=0 while IFS= read -r file; do ((file_count++)) || true if process_file "$file"; then ((success_count++)) || true else ((failure_count++)) || true log_error "Failed to process: $file" fi done < <(build_file_list) # Summary log "==========================================" log "PROCESSING COMPLETE" log "==========================================" log "Total files found: $file_count" log "Successfully processed: $success_count" log "Failed: $failure_count" log "Completed at: $(date)" } # Run main function main "$@"