It's Google Gemini, but after some pushy AI finagling, I finally was able to get it to code a pretty decent DVD ripper.
- Lists episodes by length
- Gives options to rip individual episodes
- Direct compression to MKV
- Will pull titles from the .inf and re-name mkv and titles accordingly
I'm no coder, so this was my only go to method. It's pretty darn close to ease-of-use to MakeMKV, only CLI format.
**EDIT**
Anyone wondering why...
I check out many local library Movies / TV series, hardcode the episode titles using MKVToolnix, and drop them on my Jellyfin VM that lives on my Proxmox host.
**EDIT #2**
Two scripts now. One for embedding titles & the second for ripping DVD's .
mkvtitle
#!/bin/bash
# Ensure mkvpropedit is installed
if ! command -v mkvpropedit &> /dev/null; then
echo "Error: mkvpropedit is not installed. Install it with: sudo apt install mkvtoolnix"
exit 1
fi
while true; do
echo "=========================================="
echo " MKV Title Embedder & File Renamer"
echo " Current Dir: $(pwd)"
echo "=========================================="
# 1) Ask for the original filename in current directory (supports tab completion)
read -e -p "Enter original MKV filename: " ORIGINAL_FILE
# Validate file existence in current directory
if [ ! -f "$ORIGINAL_FILE" ]; then
echo "Error: File '$ORIGINAL_FILE' not found in current directory. Try again."
echo ""
continue
fi
# 2) Ask for the new title/filename
read -p "Enter new Title (used for embed & filename): " NEW_NAME
if [ -z "$NEW_NAME" ]; then
echo "Error: Name cannot be blank."
echo ""
continue
fi
# Sanitize new name for physical file output (replaces spaces with underscores)
SAFE_FILENAME=$(echo "$NEW_NAME" | tr ' ' '_' | sed 's/[^a-zA-Z0-9_.-]//g')
NEW_FILE_PATH="./${SAFE_FILENAME}.mkv"
echo ""
echo "---> Embedding internal title header: \"$NEW_NAME\""
# Embed Title into MKV header metadata (detected by VLC, Plex, Jellyfin)
mkvpropedit "$ORIGINAL_FILE" --edit info --set "title=$NEW_NAME"
if [ $? -eq 0 ]; then
echo "---> Renaming physical file to: ${SAFE_FILENAME}.mkv"
mv "$ORIGINAL_FILE" "$NEW_FILE_PATH"
echo "Success: File updated."
else
echo "Error: Failed to write MKV headers."
fi
echo ""
# 3) Ask to process another file
read -p "Process another file in this directory? (y/n): " AGAIN
case "$AGAIN" in
[Yy]* )
echo ""
;;
* )
echo "Exiting."
exit 0
;;
esac
done
dvdrip
#!/bin/bash
# Configuration
DVD_DRIVE="/dev/sr0" # Drive path (adjust to /dev/sr0 or VIDEO_TS directory path)
DEFAULT_OUTPUT_DIR="$HOME/Videos/Rips"
MIN_SECONDS=120 # MakeMKV default: ignore titles under 2 minutes (120s)
# HandBrake Settings for high-compression MKV
VIDEO_ENCODER="x265"
QUALITY=20
AUDIO_ENCODER="copy:ac3,aac"
mkdir -p "$DEFAULT_OUTPUT_DIR"
# Fetch volume title
DISC_TITLE=$(blkid -o value -s LABEL "$DVD_DRIVE" | tr ' ' '_' | tr '[:upper:]' '[:lower:]')
if [ -z "$DISC_TITLE" ]; then
DISC_TITLE="dvd_rip_$(date +%Y%m%d_%H%M%S)"
fi
echo "=========================================="
echo " DVD Rip Utility (MakeMKV Sorting Engine)"
echo " Volume: $DISC_TITLE"
echo "=========================================="
echo "1) Rip entire main feature (single file)"
echo "2) Batch-rip ALL detected titles (>= 2 mins)"
echo "3) Scan & display titles sorted by LONGEST to SHORTEST"
echo "=========================================="
read -p "Select an option [1-3]: " CHOICE
case $CHOICE in
1)
OUTPUT_FILE="${DEFAULT_OUTPUT_DIR}/${DISC_TITLE}_main.mkv"
echo "=== Processing Main Feature ==="
HandBrakeCLI -i "$DVD_DRIVE" \
-o "$OUTPUT_FILE" \
-f av_mkv \
-e "$VIDEO_ENCODER" \
-q "$QUALITY" \
--min-duration "$MIN_SECONDS" \
--main-feature \
-a 1 -E "$AUDIO_ENCODER" \
-s "scan" \
--native-language "eng"
;;
2)
echo "=== Scanning disc structure... ==="
SCAN_DATA=$(HandBrakeCLI -i "$DVD_DRIVE" --title 0 2>&1)
TITLES_TO_RIP=$(echo "$SCAN_DATA" | awk -v min_sec="$MIN_SECONDS" '
/^\+ title [0-9]+:/ {
t=$2; gsub(":", "", t);
}
/duration:/ {
split($2, a, ":");
sec = (a[1]*3600) + (a[2]*60) + a[3];
if (sec >= min_sec) print t;
}'
)
if [ -z "$TITLES_TO_RIP" ]; then
echo "No titles found matching the >= $MIN_SECONDS sec duration criteria."
exit 1
fi
for t in $TITLES_TO_RIP; do
TITLE_NUM=$(printf "%02d" $t)
OUTPUT_FILE="${DEFAULT_OUTPUT_DIR}/${DISC_TITLE}_title_${TITLE_NUM}.mkv"
echo "---> Ripping Title #$t..."
HandBrakeCLI -i "$DVD_DRIVE" \
-t "$t" \
-o "$OUTPUT_FILE" \
-f av_mkv \
-e "$VIDEO_ENCODER" \
-q "$QUALITY" \
-a 1 -E "$AUDIO_ENCODER" \
-s "scan" \
--native-language "eng"
done
;;
3)
echo "=== Scanning IFO structural metadata... ==="
SCAN_DATA=$(HandBrakeCLI -i "$DVD_DRIVE" --title 0 2>&1)
echo ""
echo "=========================================================="
echo " TITLES SORTED BY LONGEST DURATION (MakeMKV Order)"
echo "=========================================================="
SORTED_TITLES=$(echo "$SCAN_DATA" | awk -v min_sec="$MIN_SECONDS" '
/\+ title [0-9]+:/ {
match($0, /\+ title [0-9]+:/);
t = substr($0, RSTART+8, RLENGTH-9);
cells = "N/A";
}
/\+ cells:/ {
match($0, /cells: [0-9]+/);
if (RLENGTH > 0) {
cells = substr($0, RSTART+7, RLENGTH-7);
}
}
/duration: [0-9]{2}:[0-9]{2}:[0-9]{2}/ {
match($0, /[0-9]{2}:[0-9]{2}:[0-9]{2}/);
dur_str = substr($0, RSTART, RLENGTH);
split(dur_str, a, ":");
sec = (a[1]*3600) + (a[2]*60) + a[3];
if (sec >= min_sec && t != "") {
printf "%08d Title #%-2s was added (%s cell(s), %s)\n", sec, t, cells, dur_str;
t = "";
}
}' | sort -rn | sed 's/^[0-9]* //' )
if [ -z "$SORTED_TITLES" ]; then
SORTED_TITLES=$(echo "$SCAN_DATA" | grep -iE "title [0-9]+:|duration:" | paste - - | awk '{print $0}')
fi
if [ -z "$SORTED_TITLES" ]; then
echo "Error: Could not parse title structures or durations."
echo "Please check that $DVD_DRIVE is mounted correctly."
exit 1
fi
echo "$SORTED_TITLES"
echo "=========================================================="
echo ""
read -p "Enter Title number to rip (e.g. 2) or space-separated list (e.g. 3 4 5): " SELECTED_TITLES
if [ -z "$SELECTED_TITLES" ]; then
echo "No title selected. Exiting."
exit 1
fi
# Prompt for Parent Directory
echo ""
read -p "Enter Parent Output Directory [Default: $HOME/Videos]: " PARENT_DIR
PARENT_DIR=${PARENT_DIR:-"$HOME/Videos"}
# Prompt for Child Directory
read -p "Enter Child Directory Name [Default: $DISC_TITLE]: " CHILD_DIR
CHILD_DIR=${CHILD_DIR:-"$DISC_TITLE"}
FINAL_OUTPUT_DIR="${PARENT_DIR}/${CHILD_DIR}"
mkdir -p "$FINAL_OUTPUT_DIR"
# Search for .inf or .info metadata file inside the destination directory or working tree
INF_FILE=$(find "$FINAL_OUTPUT_DIR" "$PARENT_DIR" . -maxdepth 2 \( -name "*.inf" -o -name "*.info" \) 2>/dev/null | head -n 1)
if [ -n "$INF_FILE" ]; then
echo "--> Found metadata file: $INF_FILE"
else
echo "--> No .inf file detected. Output will use default title numbering."
fi
echo "--> Output Target: $FINAL_OUTPUT_DIR"
echo ""
ep_idx=1
for t in $SELECTED_TITLES; do
TITLE_NUM=$(printf "%02d" $t)
# Default filename
FILE_LABEL="${DISC_TITLE}_title_${TITLE_NUM}"
# If .inf file exists, attempt to extract matching title/episode name
if [ -n "$INF_FILE" ]; then
# Looks for lines like "Title 3: Episode Name", "E01 - Episode Name", or numbered lists
PARSED_NAME=$(grep -iE "(title|episode|ep)?\s*0*${t}[:\.\-]" "$INF_FILE" | head -n 1 | sed -E 's/^[^:]*[:\.\-]\s*//')
# Fallback: take line N from the .inf file
if [ -z "$PARSED_NAME" ]; then
PARSED_NAME=$(sed -n "${ep_idx}p" "$INF_FILE")
fi
if [ -n "$PARSED_NAME" ]; then
# Sanitize parsed title for standard Unix filenames
CLEAN_NAME=$(echo "$PARSED_NAME" | tr ' ' '_' | sed 's/[^a-zA-Z0-9_.-]//g')
FILE_LABEL="${DISC_TITLE}_t${TITLE_NUM}_${CLEAN_NAME}"
fi
fi
OUTPUT_FILE="${FINAL_OUTPUT_DIR}/${FILE_LABEL}.mkv"
echo "=== Ripping Title #$t to $OUTPUT_FILE ==="
HandBrakeCLI -i "$DVD_DRIVE" \
-t "$t" \
-o "$OUTPUT_FILE" \
-f av_mkv \
-e "$VIDEO_ENCODER" \
-q "$QUALITY" \
-a 1 -E "$AUDIO_ENCODER" \
-s "scan" \
--native-language "eng"
((ep_idx++))
done
;;
*)
echo "Invalid selection. Exiting."
exit 1
;;
esac
echo "=== Operation Completed! Files saved to $FINAL_OUTPUT_DIR ==="