Awhile back I found a stack of audio CDs I wished to digitize. It’s a bit of work to do the following steps quickly and while doing other more cerebral work:

  1. Open the disc tray
  2. Insert the disc
  3. Wait a few seconds for the disc to be detected by the OS
  4. Kick off abcde to rip the CD.
  5. Repeat ad infinitum.

The solution is to streamline the workflow:

  1. Open the disc tray (first time only)
  2. Insert the disc
  3. Wait until abcde is finished and ejects the CD, repeat from step #2.

Much simpler. This requires only one human action, whereas the above requires a bit of fumbling with telling the workstation to do the work. Read on to learn how to set this up for your own ‘ripping fun’.

§Tell udev to do something when the CD drive is ejected or inserted

Add this to your udev rules. On my box it is located at /etc/udev/rules.d/90-ripper.rules.

SUBSYSTEM=="block", KERNEL=="sr*", ACTION=="change", RUN+="/usr/local/sbin/rip-music-cd"

Next run udevadm control --reload to ask udev to look for new rules off disk.

§The script that udev runs

Create this script at /usr/local/sbin/rip-music-cd and make it executable (i.e. chmod 755 /usr/local/sbin/rip-music-cd).

#!/usr/bin/env bash
set -eu -o pipefail

ripuser=winston

if [[ -f /root/dont-rip ]]; then
    msg='/root/dont-rip exists, exiting.'
    echo "$msg"
    sudo -u "$ripuser" -i sh -c 'cat >> ~/ripper.log' <<<"$msg"
    exit 1
fi

sleep 5

# https://superuser.com/a/1367091/302931
python - <<EOF
"""detect_tray reads status of the CDROM_DRIVE.
Statuses:
1 = no disk in tray
2 = tray open
3 = reading tray
4 = disk in tray
"""

import fcntl
import os
import sys

CDROM_DRIVE = '/dev/sr0'

fd = os.open(CDROM_DRIVE, os.O_RDONLY | os.O_NONBLOCK)
rv = fcntl.ioctl(fd, 0x5326)
os.close(fd)
sys.exit(rv != 4)
EOF

# Not reached when the ioctl retval is not equal to 4 (disk in tray).

sudo -u "$ripuser" -i bash -c \
     'env INTERACTIVE=no abcde -B -G -x -d /dev/sr0 &>> ~/ripper.log'

Let’s break this script down.

  • #!/usr/bin/env bash tells the OS to run this script using the bash program, wherever it is located in your $PATH.
  • set -eu -o pipefail tells Bash to exit on unhandled error, raise an error when an unbound variable is dereferenced, and propagate pipeline failures to the shell environment.
  • The [[ -f /root/dont-rip ]] test exits early if the file /root/dont-rip exists. This is intended to disable the ripping tool when it is not in use - e.g. when watching DVDs or working with data CDs.
  • sleep 5 I forgot why this is needed, I think it helps the device settle. Maybe this should be waiting for another udev event? :thinking:
  • The python script opens the CDROM device, sends an ioctl to test the status of the disc tray (fcntl.ioctl(fd, 0x5326)). Then passes a non-zero exit code back to the shell when there is a disc in tray (sys.exit(rv != 4)).
  • Finally the abcde incantation kicks off the CD ripping program. The env INTERACTIVE=no is important because it tells abcde to never prompt for user input. See the flag breakdown as follows:
    • -B: Embed album art
    • -G: Download album art
    • -x: Eject the CD after completion.
    • -d /dev/sr0: Specify the CDROM drive device.

You’ll also note this appears to log to a ripper.log file. Find this in your homedirectory of the $ripuser.

There are three settings uncommented in my abcde config file. I recommend settings all of these to some value:

OUTPUTDIR=/mnt/stuff/rips
WAVOUTPUTDIR=/tmp
OUTPUTTYPE=flac
  • OUTPUTDIR is the output directory tree where finished rips should be placed.
  • WAVOUTPUTDIR is a temporary directory where the intermediate wav files should be placed prior to transcoding to whatever output format.
  • OUTPUTTYPE is the format you wish to store your music in. Highly recommend flac or other lossless, otherwise you may as well pack up and go home, listen to music off YouTube instead.

§Conclusion

Lug a stack of discs to your workstation, and get cracking - they won’t rip themselves!

Run this as your $ripuser user to monitor activity and progress:

tail -f ~/ripper.log

§Sources