#!/bin/bash debug() { echo "๐Ÿ› ๏ธ DEBUG: $*" >&2; } # Convert .avi and .mkv files to .mp4 # Supports --dry-run to simulate actions # --source is required; --archive is optional set -euo pipefail # --- Defaults --- DRY_RUN=false DEBUG=false SRC_DIR="" ARCHIVE_DIR="" LOG_FILE="log.convert" QUALITY="23" SRC_TYPES=() # --- Help Message --- show_help() { echo "Usage: $0 --source [--archive ] [--quality ] [--dry-run] [--debug]" echo echo " --source Directory to scan for .avi and .mkv files" echo " --archive Directory to move originals after conversion (optional)" echo " If omitted, originals will be deleted" echo " --quality Quality of the video 18-28 (18 is high quality, 23 is default, 28 is low)" echo " --dry-run Show actions without performing them" echo " --debug Output debug statements" echo exit 1 } # --- Parse Arguments --- while [[ $# -gt 0 ]]; do case "$1" in --source) SRC_DIR="$(realpath "$2")" shift 2 ;; --archive) ARCHIVE_DIR="$(realpath "$2")" shift 2 ;; --dry-run) DRY_RUN=true shift ;; --debug) DEBUG=true shift ;; --src-type) SRC_TYPES+=("${2,,}") # normalize to lowercase shift -2 ;; --help|-h) show_help ;; --quality) QUALITY="$2" if ! [[ "$QUALITY" =~ ^[0-9]+$ ]] || ((QUALITY < 18 || QUALITY > 28)); then echo "โŒ Error: --quality must be an integer between 18 and 28." >&2 exit 1 fi shift 2 ;; *) echo "Unknown option: $1" show_help ;; esac done if [[ "${#SRC_TYPES[@]}" -eq 0 ]]; then SRC_TYPES=("avi" "mkv") # default id none specified fi # --- Validate Source Directory --- if [[ -z "$SRC_DIR" ]]; then echo "โŒ Error: --source is required." >&2 show_help fi if [[ ! -d "$SRC_DIR" ]]; then echo "โŒ Error: Source directory does not exist: $SRC_DIR" >&2 exit 1 fi echo "๐Ÿ” Scanning: $SRC_DIR" $DRY_RUN && echo "๐Ÿงช Dry-run mode enabled" # --- Gather file list once --- mapfile -d '' FILE_LIST < <(find "$SRC_DIR" \( -iname '*.avi' -o -iname '*.mkv' \) -print0) # --- Preview list if in debug mode --- if [[ "${DEBUG:-false}" == "true" ]]; then echo "๐Ÿ—‚๏ธ DEBUG: File list to process:" for file in "${FILE_LIST[@]}"; do echo " - $file" done fi # --- Find and Process Files --- # Using process substitution instead of a pipe, so the while loop runs in the current shell for src_file in "${FILE_LIST[@]}"; do # abs_src_file="$(realpath "$src_file")" # rel_path="${abs_src_file#$SRC_DIR/}" # Use absolute path If the source file path is not absolute # (doesn't start with '/'), prepend the source directory to # make it an absolute path. [[ "$src_file" != /* ]] && src_file="$SRC_DIR/$src_file" rel_path="${src_file#$SRC_DIR/}" base_name="${rel_path%.*}" dst_file="$SRC_DIR/$base_name.mp4" echo "โ–ถ๏ธ Converting: $rel_path" if $DRY_RUN; then echo " [dry-run] Would convert to: $dst_file" [[ -n "$ARCHIVE_DIR" ]] && echo " [dry-run] Would move original to: $ARCHIVE_DIR/$rel_path" || echo " [dry-run] Would delete original" continue fi # Create output directory if needed mkdir -p "$(dirname "$dst_file")" if [[ "${DEBUG:-false}" == "true" ]]; then debug "dirname dst_file: $(dirname "$dst_file")" debug "src_file: $src_file" # debug "abs_src_file: $abs_src_file" debug "rel_path: $rel_path" debug "base_name: $base_name" debug "dst_file: $dst_file" debug "" debug "command:" debug "ffmpeg -i \"$src_file\" -c:v libx264 -preset slow -crf $QUALITY -c:a aac -b:a 128k -movflags +faststart \"$dst_file\" -y >> $LOG_FILE 2>&1" fi # Convert video with ffmpeg and log output ffmpeg \ -i "$src_file" \ -c:v libx264 \ -preset slow \ -crf "$QUALITY" \ -c:a aac \ -b:a 128k \ -movflags +faststart \ "$dst_file" \ -y >> "$LOG_FILE" 2>&1 # Archive or delete original if [[ -n "$ARCHIVE_DIR" ]]; then archive_file="$ARCHIVE_DIR/$rel_path" mkdir -p "$(dirname "$archive_file")" mv "$src_file" "$archive_file" else rm "$src_file" fi done echo "โœ… Done."