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:
- Open the disc tray
- Insert the disc
- Wait a few seconds for the disc to be detected by the OS
- Kick off
abcde
to rip the CD. - Repeat ad infinitum.
The solution is to streamline the workflow:
- Open the disc tray (first time only)
- Insert the disc
- 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 thebash
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. Theenv 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
.
§Recommended settings for ~/.abcde.conf
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 recommendflac
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