Created
January 23, 2026 19:18
-
-
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
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 | |
| # 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