Automatically ripping CDs on linux

Part 2 of building a music server from scratch. See here for part 1.

  1. This is a work in progress. Use at your own peril.

  2. To auto-rip the cds inserted into my headless music server I’m going to use abcde and cdparanoia. Install them with
    sudo apt-get install abcde lame eyed3 glyrc imagemagick cdparanoia1

  3. MusicBrainz is missing some dependencies so run sudo apt-get install libmusicbrainz-discid-perl libwebservice-musicbrainz-perl

  4. After running abcde-musicbrainz-tool from the command line I got an error about missing Mojo::DOM. Fix this with sudo cpan install Mojo::DOM

  5. We need to a config file for abcde. This is abcde.conf. There are multiple places that this could go (see the man page), but since I have a single user setup for now I am placing this in my users home directory /home/phil/.abcde.conf, but I could place it anywhere else and get the program to read it with the -c [filename] option. Placing it here means if I run abcde from the command line, this will be the default configuration.

  6. Edit the your .abcde.conf file to use the correct ripper and write to files to the correct place.

    CDROMREADERSYNTAX=cdparanoia
    CDPARANOIAOPTS="--never-skip=40"
    OUTPUTTYPE=flac
    #edit OUTPUTDIR. Its the location of the output flacs to write
    OUTPUTDIR=/home/phil/storage/music/flac/abcde
    #you might need to change CDROM
    CDROM=/dev/sr0
    EJECTCD=y
    CDDBMETHOD=musicbrainz
    GLYRC=glyrc
    CONVERT=convert
    ALBUMRARTFILE="cover.jpg"
    ALBUMARTTYPE="JPEG"
    # Make a local cache of cddb entries and then volunteer to use
    # these entries when and if they match the cd:
    CDDBCOPYLOCAL="y"
    CDDBLOCALDIR="$HOME/.cddb"
    CDDBLOCALRECURSIVE="y"
    CDDBUSELOCAL="y"
    FLAC=flac                                 # Path to FLAC encoder
    # Specify the encoder to use for FLAC. In this case
    # flac is the only choice.
    FLACENCODERSYNTAX=flac
    
    # Specify the path to the selected encoder. In most cases the encoder
    # should be in your $PATH as I illustrate below, otherwise you will
    # need to specify the full path. For example: /usr/bin/flac
    FLAC=flac
    
    # Specify your required encoding options here. Multiple options can
    # be selected as '--best --another-option' etc.
    # Overall bitrate is about 880 kbs/s with level 8.
    FLACOPTS='-s -e -V -8'
    # Give the location of the CD identification program:
    CDDISCID=cd-discid
    # The default actions that abcde will take.
    ACTIONS=cddb,playlist,read,getalbumart,encode,tag,move,clean
    
    # If NOSUBMIT is set to y, then abcde will never prompt asking if you
    # wish to submit your edited cddb file.
    NOSUBMIT=n
    # Decide here how you want the tracks labelled for a standard 'single-artist',
    # multi-track encode and also for a multi-track, 'various-artist' encode:
    OUTPUTFORMAT='${OUTPUT}/${ARTISTFILE}-${ALBUMFILE}/${TRACKNUM}.${TRACKFILE}'
    VAOUTPUTFORMAT='${OUTPUT}/Various-${ALBUMFILE}/${TRACKNUM}.${ARTISTFILE}-${TRACKFILE}'
    # This function takes out dots preceding the album name, and removes a grab
    # bag of illegal characters. It allows spaces, if you do not wish spaces add
    # in -e 's/ /_/g' after the first sed command.
    mungefilename ()
    {
    
        CDDISKID=$(cd-discid "$CDROM")
        FIRSTPART=$(echo $CDDISKID | cut -d' ' -f1)
        echo $FIRSTPART "$@" | /home/phil/dev/munger.py
    }
    
    MAXPROCS=2                                # Run a few encoders simultaneously
    PADTRACKS=y                               # Makes tracks 01 02 not 1 2
    EXTRAVERBOSE=2                            # Useful for debugging
    INTERACTIVE=n
    post_read()
    {
    
    }
    #ok, now we need to set the correct ownership for the flac files.
    post_encode ()
    {
        ARTISTFILE="$(mungefilename "$TRACKARTIST")"
        ALBUMFILE="$(mungefilename "$DALBUM")"
    
        if [ "$VARIOUSARTISTS" = "y" ] ; then
            FINDPATH="$(eval echo "$VAOUTPUTFORMAT")"
        else
            FINDPATH="$(eval echo "$OUTPUTFORMAT")"
        fi
    
    FINALDIR="$(dirname "$OUTPUTDIR/$FINDPATH")"
    C_CMD=(chown -R phil:lms "$FINALDIR")
    #echo "${C_CMD[@]}" >>tmp2.log
    "${C_CMD[@]}"
    }
    

    munger.py strips any odd unicode out of the string to make the filename safe. It can be donwnloaded from here
    Most of the above is straight forwards abcde configuration. It uses cdparanoia to rip the cd, musicbrainz to find the track information and then it uses flac encoding. The mungefilename() function removes any unwanted characters from the file name and also appends the discID number onto the directory name for any unknown album – otherwise abcde will overwrite it. The post_encode() function changes the ownership of the files to phil:lms. You will need to edit this. TODO: Currently this is placing all the temp files in / which might be a problem.

  7. The ownship of the directory /home/phil/storage/music/flac/abcde and /home/phil/storage/music/flac/abcde/flac will need correcting with sudo chmod -R /home/phil/storage/music/flac/abcde

  8. Test the configuration by inserting a CD and ripping it from the command line. abcde -c /home/phil/.abcde.conf

  9. Ok, so how to get it to autorip when a CD is inserted. For this the udev is needed which is the Linux device manager. We need to get to trigger a command when it detects a CD is inserted. However, udev does not allow long running programmes to be run. If you try to call abcde directly you will find udev kills the process before its finished ripping. To get around this we create a new service and get udev to start the service.

  10. Create a new file /etc/systemd/system/ripper.service with the following

    [Unit]
    Description=Auto CD ripper
    
    [Service]
    Type=oneshot
    ExecStart=/home/phil/dev/rip.sh
    
    [Install]
    WantedBy=multi-user.target
    
  11. And create the /home/phil/dev/rip.sh file which gets run

    #!/bin/bash
    LOCKDIR=rip.lock
    LOGFILE=/home/phil/dev/log.txt
    
    function cleanup {
    
     if rmdir $LOCKDIR; then
        echo "Finished $(date)" >> $LOGFILE
    
    else
        echo "Failed to remove lock dir '$LOCKDIR'" >> $LOGFILE
        exit 1
    
    fi
    
    }
    
    if mkdir $LOCKDIR; then
        trap "cleanup" EXIT
        echo "Acquired Lock, running" >> $LOGFILE
        echo "Started $(date) $$" >> $LOGFILE
        abcde -c /home/phil/.abcde.conf
        #trigger LMS rescan
        wget -q --spider http://localhost:9000/settings/index.html?p0=rescan
        echo "Stopped $(date) $$" >> $LOGFILE
        # optional notifications, uncomment if needed
        # echo "Sending IFFT webhook:">>$LOGFILE
        # /home/phil/dev/ifttt_send.sh "Ripper done. Please eject CD" >> $LOGFILE
        # send the logfile by email, you need to configure mail  first for this to work
        # cat $LOGFILE | mail -s "Ripper log" your_email@example.com
    else
        echo "Could not create lock, already running? '$LOCKDIR'"
        exit 1
    
    fi
    

    This file basically only allows abcde to run as a single instance. It needs to be made executable with chmod u+x /home/phil/dev/rip.sh
    The wget command triggers the LMS rescan which looks for new media. The LOGFILE is really only useful to see if the script is working, you might want to set it to /dev/null eventually.
    The ifttt_send.sh used IFTT to send a notification that the process has finished to my phone. You need an IFTT account. I created a Maker Event called ripper_done that connect to a rich text notification. The ifttt_send.sh contains:

    #!/bin/bash
    source /home/phil/dev/ifttt_key.sh
    CMDSTRING=("curl -H \"Content-Type: application/json\" -X POST -d '{\"value1\":\" $1\"}' https://maker.ifttt.com/trigger/ripper_done/with/key/$IFTTT_KEY")
    eval $CMDSTRING
    

    ifttt_key.sh defines IFTTT_KEY which you need. It a single line export IFTTT_KEY="YOUR KEY VALUE"

  12. Start the service

    sudo systemctl daemon-reload
    sudo systemctl enable ripper.service
    

    Now if you place a CD in the drive you should be able to rip it by running sudo systemctl start ripper. The status of the ripping can be obtained with systemctl status ripper

  13. We now make a udev rule to trigger the service start when a cd is inserted. Create a file /etc/udev/rules.d/99-cd-audio-rip.rules with the following

    SUBSYSTEM=="block",KERNEL=="sr0",ACTION=="change",RUN+="/bin/systemctl start ripper"
    

    sr0 is the name of my CD-ROM drive.
    Now add the rule with sudo udevadm control --reload

  14. Now when you insert a CD it should autorip. You can always test the progress of the ripping with systemctl status ripper

1: My apt-get version of abcde was 2.9 which has a bug and crashes if Musicbrainz returns an unknown disc. To fix it we need to update it. Remove abcde with sudo apt-get remove abcde, download the latest version from here 2.9.2 at the time of writing. Untar it, enter the directory and run sudo make install. Since we removed the managed version of abcde the dependencies will be marked for removal. To stop this install them sudo apt-get install vorbis-tools cd-discid fdkaac. You might want to check for other dependencies if you have a problems with apt-cache depends abcde.

view raw

cd_ripper.md

hosted with ❤ by GitHub

3 Replies to “Automatically ripping CDs on linux”

  1. Hi,

    first of all, thanks for putting this together. It works almost perfect. The only problem I have is with the last track of every disc I rip this way. That track gets put in its own folder with a part of the artists or albums name. I can’t see any system in what name is given to the folder. The file is fine though and just needs to be copied over. Any Idea what could cause this?

    Regards,
    Romano

    1. Hmm, this bug rings a bell and I think I fixed it. My system uses a slightly different version to this one so I’ll see if I can find the problem and update when I can.

      1. Nice! Thank you! I’m currently trying to figure it out and will update as soon as I find something. One thing I noticed is a problem with the empty post_read() in the .absde.conf. But removing it didn’t fix the problem

Leave a Reply

Your email address will not be published. Required fields are marked *