Skip to content

Instantly share code, notes, and snippets.

@ephrin
Last active April 18, 2025 17:46
Show Gist options
  • Save ephrin/fac4c279a411c07e29bfebc541e4529d to your computer and use it in GitHub Desktop.
Save ephrin/fac4c279a411c07e29bfebc541e4529d to your computer and use it in GitHub Desktop.
Mkv to AVI convert script. Requries ffmpeg
#!/bin/bash
# --- Configuration ---
# Video codec for AVI (libxvid is widely compatible)
VIDEO_CODEC="libxvid"
# Video quality (lower is better, 2-5 is often good for Xvid)
VIDEO_QUALITY="3"
# Audio codec for AVI (libmp3lame is widely compatible)
AUDIO_CODEC="libmp3lame"
# Audio quality (lower is better, 4-6 is often good for MP3)
AUDIO_QUALITY="4"
# --- Helper Functions ---
log_info() {
echo "[INFO] $1"
}
log_warn() {
echo "[WARN] $1" >&2 # Send warnings to standard error
}
log_error() {
echo "[ERROR] $1" >&2 # Send errors to standard error
exit 1
}
# --- Pre-requisite Checks ---
# Check if ffmpeg is installed
if ! command -v ffmpeg &> /dev/null; then
log_error "ffmpeg could not be found. Please install it (e.g., 'sudo apt update && sudo apt install ffmpeg')."
fi
# --- Argument Handling ---
# Check if the correct number of arguments is provided
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <source_directory> <destination_directory>"
echo "Example: $0 \"/path/to/mkv_files\" \"/path/to/output_avi\""
exit 1
fi
SOURCE_DIR="$1"
DEST_DIR="$2"
# --- Directory Validation ---
# Check if source directory exists and is a directory
if [ ! -d "$SOURCE_DIR" ]; then
log_error "Source directory '$SOURCE_DIR' not found or is not a directory."
fi
# Check if destination directory exists, create it if not
if [ ! -d "$DEST_DIR" ]; then
log_info "Destination directory '$DEST_DIR' not found. Attempting to create it."
mkdir -p "$DEST_DIR"
if [ $? -ne 0 ]; then
log_error "Failed to create destination directory '$DEST_DIR'."
fi
log_info "Destination directory '$DEST_DIR' created successfully."
fi
# --- Core Processing Logic ---
log_info "Starting MKV to AVI conversion process."
log_info "Source Directory: $SOURCE_DIR"
log_info "Destination Directory: $DEST_DIR"
# Find all .mkv files in the source directory (non-recursively)
# Use null delimiter for safety with filenames containing special characters
find "$SOURCE_DIR" -maxdepth 1 -type f -name '*.mkv' -print0 | while IFS= read -r -d $'\0' mkv_file; do
# Get the base filename without path and extension
base_name_with_ext=$(basename "$mkv_file")
base_name="${base_name_with_ext%.*}"
log_info "Processing: $base_name_with_ext"
# --- Extract Simplified Name and Season/Episode ---
simplified_name="$base_name" # Default to original base name
season_episode_info="" # Default to empty
# Pattern 1: SxxExx (e.g., S01E05, S01.E05, S01_E05) - Case Insensitive
if [[ "$base_name" =~ ([Ss]([0-9]+)[ ._]?([Ee]([0-9]+))) ]]; then
match="${BASH_REMATCH[0]}" # Full SxxExx pattern matched
season_num=$(printf "%02d" "${BASH_REMATCH[2]}") # Pad season with zero if needed
episode_num=$(printf "%02d" "${BASH_REMATCH[4]}") # Pad episode with zero if needed
season_episode_info="S${season_num}E${episode_num}"
# Extract name part before the match
name_part="${base_name%%"$match"*}"
# Clean up name part: replace dots/underscores with spaces, trim trailing junk
simplified_name=$(echo "$name_part" | sed -E 's/[._]+$//; s/[._]/ /g; s/ +$//; s/^ +//')
# Pattern 2: NxNN (e.g., 1x05, 01x05) - Often delimited by space, dot, or underscore
elif [[ "$base_name" =~ ([ ._]([0-9]+)x([0-9]+)) ]]; then
match="${BASH_REMATCH[0]}" # Full pattern matched (including delimiter)
season_num=$(printf "%02d" "${BASH_REMATCH[2]}") # Pad season
episode_num=$(printf "%02d" "${BASH_REMATCH[3]}") # Pad episode
season_episode_info="S${season_num}E${episode_num}"
# Extract name part before the match
name_part="${base_name%%"$match"*}"
# Clean up name part
simplified_name=$(echo "$name_part" | sed -E 's/[._]+$//; s/[._]/ /g; s/ +$//; s/^ +//')
else
log_warn " -> Could not automatically extract Season/Episode info for '$base_name'. Using full base name."
# Keep default simplified_name and empty season_episode_info
fi
# Construct the final output base name (replace spaces with underscores for filename safety)
output_base_name=$(echo "${simplified_name} ${season_episode_info}" | sed 's/ /_/g' | sed 's/__*/_/g') # Replace spaces, collapse multiple underscores
output_avi_path="$DEST_DIR/${output_base_name}.avi"
output_info_path="$DEST_DIR/${output_base_name}.txt"
# --- Check for Existing File and Ask to Overwrite ---
overwrite=true # Default to overwrite unless file exists and user says no
if [ -f "$output_avi_path" ]; then
read -p " -> Output file '$output_avi_path' already exists. Overwrite? (y/N): " -n 1 -r reply
echo # Move to a new line after input
if [[ ! "$reply" =~ ^[Yy]$ ]]; then
log_info " -> Skipping conversion for '$base_name_with_ext'."
overwrite=false
fi
fi
# --- Perform Conversion if Overwriting ---
if [ "$overwrite" = true ]; then
log_info " -> Converting '$base_name_with_ext' to '$output_avi_path'..."
log_info " Using Video: $VIDEO_CODEC (Quality: $VIDEO_QUALITY), Audio: $AUDIO_CODEC (Quality: $AUDIO_QUALITY)"
# Use ffmpeg to convert. -hide_banner reduces noise. -loglevel error shows only critical errors.
# -c:v specifies video codec, -qscale:v video quality
# -c:a specifies audio codec, -qscale:a audio quality
# -y automatically overwrites output without asking (we handle the prompt ourselves)
ffmpeg -hide_banner -loglevel error -i "$mkv_file" \
-c:v "$VIDEO_CODEC" -qscale:v "$VIDEO_QUALITY" \
-c:a "$AUDIO_CODEC" -qscale:a "$AUDIO_QUALITY" \
-y "$output_avi_path"
# Check if ffmpeg command was successful
if [ $? -eq 0 ]; then
log_info " -> Conversion successful: $output_avi_path"
# --- Create Simplified Info File ---
extracted_info_text="Original Filename: ${base_name_with_ext}\nSimplified Name: ${simplified_name}\nSeason/Episode: ${season_episode_info:-N/A}"
echo -e "$extracted_info_text" > "$output_info_path"
if [ $? -eq 0 ]; then
log_info " -> Saved extracted info to: $output_info_path"
else
log_warn " -> Failed to write info file: $output_info_path"
fi
else
log_warn " -> Conversion FAILED for '$base_name_with_ext'. Check ffmpeg output or source file."
# Optional: Clean up partially created/failed output file
# rm -f "$output_avi_path"
fi
fi
done # End of loop processing MKV files
log_info "Processing complete."
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment