Last active
April 18, 2025 17:46
-
-
Save ephrin/fac4c279a411c07e29bfebc541e4529d to your computer and use it in GitHub Desktop.
Mkv to AVI convert script. Requries ffmpeg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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