Skip to content

Instantly share code, notes, and snippets.

@atetzner
Created January 23, 2026 19:18
Show Gist options
  • Select an option

  • Save atetzner/d520f35ac3a430969ba20a0dedda63ea to your computer and use it in GitHub Desktop.

Select an option

Save atetzner/d520f35ac3a430969ba20a0dedda63ea to your computer and use it in GitHub Desktop.
Script for automatic ripping of older/broken CDs and DVDs to disk with data rescue
#!/bin/bash
# This script is intended to rescue data from older CDs and DVDs, which are only partially readable.
# It uses ddrescue to store an image of such disks to your machine and after that tries to extract
# data from the images:
# - Data disks are just copied using rsync
# - VCDs/SVCDs and Video DVDs are ripped. All DVD titles with >3min are extracted to get the main
# title and bonus material
# - Audio disks are ripped to OGG using abcde
#
# The script is optimized for reading out large amounts of disks: It waits for a disk to get inserted,
# reads out the disk, ejects the drive and then waits for the next disk to get inserted. To interaction
# with the keyboard / mouse required.
# For every disk, the percentage of rescued data is outputted.
#
# Required tools:
# - python3
# - beep
# - abcde
# - gddrescue
# - vcdimager
# - rsync
# - Handbrake-Cli
# - testdisk
# - MakeMkv
#
# Install them using:
# sudo add-apt-repository ppa:heyarje/makemkv-beta
# sudo apt update
# sudo apt install -y \
# beep \
# abcde lame id3v2 eyed3 flac vorbis-tools \
# gddrescue \
# vcdimager \
# handbrake-cli \
# testdisk \
# makemkv-bin makemkv-oss
if [ $(whoami) != "root" ] ; then
echo Script must be executed as root
exit 0
fi
function beep_root() {
(
unset SUDO_GID SUDO_COMMAND SUDO_USER SUDO_UID
beep
)
}
function driveStatus() {
python3 -c "import fcntl, os; d=os.open('$1', os.O_RDONLY | os.O_NONBLOCK); print(fcntl.ioctl(d, 0x5326)); os.close(d)"
}
ctr=1
mnt=$PWD/mnt
diskDevice=/dev/sr0
owner=1000
###################################
# Parse input arguments
###################################
while [[ $# -gt 0 ]]; do
case $1 in
-d|--device)
diskDevice="$2"
shift
shift
;;
-m|--mount-dir)
mnt="$2"
shift
shift
;;
-c|--counter)
ctr="$2"
shift
shift
;;
-o|--owner)
owner="$2"
shift
shift
;;
-h|--help)
echo "$0 ([-c|--counter] START_COUNTER) ([-m|--mount-dir] TEMP_MOUNT_DIR) ([-d|--device] DISK_DEVICE) ([-o|--owner] FILE_OWNER)"
echo
echo "START_COUNTER defaults to $ctr"
echo "TEMP_MOUNT_DIR defaults to $mnt"
echo "DISK_DEVICE defaults to $diskDevice"
echo "OWNER defaults to $owner"
exit 0
;;
*)
echo "Unknown option $1"
exit 1
;;
esac
done
###################################
# Read out the disks
###################################
while true ; do
if [ -d $ctr ] || [ -d ${ctr}_* ] ; then
ctr=$(( $ctr + 1 ))
continue
fi
if [ $(driveStatus "$diskDevice") != "4" ] ; then
echo No Disk in drive
while [ $(driveStatus "$diskDevice") != "4" ] ; do
sleep 5
done
echo
fi
workingFolder="$ctr"
diskLabel="$(blkid -s LABEL -o value "$diskDevice")"
if [ "$diskLabel" != "" ] ; then
workingFolder="${workingFolder}_${diskLabel}"
fi
echo "Reading disk to $workingFolder"
mkdir "$workingFolder"
pushd "$workingFolder" &>/dev/null
if udevadm info -q env -n "$diskDevice" | grep -q "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO" ; then
echo Reading out audio CD
while true ; do
if [ ! -e abcde.log ] || grep -q "SSL connect attempt failed" abcde.log ; then
abcde -d "$diskDevice" -G -N -p &> abcde.log
elif grep -q "abcde-musicbrainz-tool failed to run" abcde.log ; then
echo CDDBMETHOD=cddb,cdtext > abcde.conf
echo CDDBURL=http://freedb.dbpoweramp.com:80/~cddb/cddb.cgi >> abcde.conf
abcde -d "$diskDevice" -N -p -c abcde.conf &> abcde.log
rm abcde.conf
else
break
fi
done
else
ddrescue -n "$diskDevice" disk.img > ddrescue.log
cat ddrescue.log | grep "pct rescued" | tail -n 1 | cut -d "," -f 1
mkdir -p "$mnt"
mount -o loop,ro disk.img "$mnt"
if mount | grep "$mnt" &> /dev/null ; then
if [ -d "$mnt"/VIDEO_TS ] ; then
echo "Extracting main title from video DVD"
# Use makemkvcon as it just remuxes the video to MKV and not transcodes it.
makemkvcon mkv iso:disk.img all ./ --minlength=$(( 3 * 60 )) &> makemkvcon.log
# As an alternative to makemkvcon: Use Handbrake, which will not not remux but transcode the video
# TITLES=$(HandBrakeCLI --input disk.img --title 0 --min-duration $(( 3 * 60 )) --scan 2>&1 | grep "+ title " | sed 's/.*title \(.*\):/\1/' | tr '\n' ' ')
#
# echo "Folgende Titel werden verarbeitet: $TITLES"
#
# for TITLE_ID in $TITLES
# do
# echo "Verarbeite Titel Nummer: $TITLE_ID"
#
# HandBrakeCLI --input disk.img \
# --title "$TITLE_ID" \
# --preset "Matroska/H.264 MKV 1080p30" \
# --all-audio \
# --all-subtitles \
# --output "$PWD/title_$TITLE_ID.mkv" &> handbrake_$TITLE_ID.log
# done
elif [ -d "$mnt"/svcd ] ; then
echo "Extracting main title from (S)VCD"
vcdxrip -i disk.img &> vcdxrip.log
else
echo "Extracting files from data disk"
rsync -av "$mnt"/ ./disk-files &> rsync.log
fi
fi
# if [ $(ls -1 | wc -l) -eq 0 ] ; then
# photorec "$diskDevice"
# fi
umount "$mnt"
rmdir "$mnt"
fi
popd &>/dev/null
chown -R $owner "$workingFolder"
chmod -R u+w "$workingFolder"
df -h "$workingFolder" | sed 's/^/ /g'
echo Finished
beep_root
eject "$diskDevice"
echo
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment