Skip to content

Instantly share code, notes, and snippets.

@ZedThree
Created September 23, 2025 20:38
Show Gist options
  • Select an option

  • Save ZedThree/6b61903b208bb5f427c750041618977e to your computer and use it in GitHub Desktop.

Select an option

Save ZedThree/6b61903b208bb5f427c750041618977e to your computer and use it in GitHub Desktop.
Auto-rip music CDs
SUBSYSTEM=="block", KERNEL=="sr*", ACTION=="change", RUN+="/usr/local/sbin/rip-music-cd.py '%E{DEVNAME}'"

Auto-rip music CDs

See Winny's Blog for the details on how to set this up. You'll need to install abcde and eyeD3 at least.

I've made some changes to work with multiple CD drives at once. I found a few old drives lying around at work, and this made things dead simple to rip several CDs at once with minimal effort.

90-ripper.rules

This goes in /etc/udev/rules.d/. Drop it in and run:

$ udevadm control --reload

rip-music-cd.py

This goes in /usr/sbin/local/. Don't forget to change USER in the script, and make it executable:

$ chmod +x /usr/sbin/local/rip-music-cd.py
#!/usr/bin/env python3
# Inspired by https://blog.winny.tech/posts/auto-rip_music_cds/
import argparse
import fcntl
import logging
import os
import pathlib
import subprocess
import sys
import time
MAX_TRIES = 10
USER = "peter"
OUTPUT_DIR = f"/home/{USER}/Music/"
logger = logging.getLogger("rip-music-cd")
logging.basicConfig(
filename=f"{OUTPUT_DIR}/rip-music-cd.log",
level=logging.INFO,
format="%(asctime)s %(message)s",
)
def is_cd_in_drive(drive: str) -> bool:
"""Return True if there's a CD in `drive`
https://superuser.com/a/1367091/302931
"""
fd = os.open(drive, os.O_RDONLY | os.O_NONBLOCK)
# This magic number checks the status
rv = fcntl.ioctl(fd, 0x5326)
os.close(fd)
logger.info("Checking drive %s: status: %d", drive, rv)
# 4 means disk in tray
return rv == 4
def run_abcde(drive: str) -> None:
drive_name = pathlib.Path(drive).name
abcde_command = (
f"env INTERACTIVE=no abcde -B -G -x -d {drive} &>> {OUTPUT_DIR}/ripper_{drive_name}.log"
)
status, out = subprocess.run(f"sudo -u {USER} -i bash -c '{abcde_command}'", text=True, shell=True)
logger.info("abcde: %s", out)
if status != 0:
sys.exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser("Auto-rip CDs")
parser.add_argument("drive", type=str, help="Path to drive")
args = parser.parse_args()
drive = args.drive
if pathlib.Path(f"/home/{USER}/norip").exists():
logger.info("norip file exists, exiting")
sys.exit()
logger.info("Started for drive %s", drive)
for _ in range(MAX_TRIES):
time.sleep(1)
if is_cd_in_drive(drive):
logger.info("Found CD in drive %s", drive)
run_abcde(drive)
logger.info("Finished ripping CD in drive %s", drive)
sys.exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment