initial comment
This commit is contained in:
295
reencode-movies.sh
Normal file
295
reencode-movies.sh
Normal file
@@ -0,0 +1,295 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user