Saturday, 7 December 2013

Raspberry Pi - Auto CD Ripper

I've got a lot of CD's I have never got round to ripping to MP3.  I didn't fancy sitting in front of a PC shovelling them in for hours, so I wrote I little program to automate the ripping using an raspberry pi and an old USB CD/DVD drive.

USB CD/DVD drives are pretty cheap nowadays, check out Amazon (UK, US), if you get one without a separate power supply, you will also need a powered USB hub.

When my program runs, it opens the cd drawer and waits for a CD to be put in, when a CD is inserted it will rip the CD and when finished open the drawer so you can put in the next.  The plan is to stick it in by the tv and just let it rip.


Its a pretty simple python program which starts up a great command line cd ripper called Ripit plus an mp3 encoder called lame.

If you want to have a go yourself.

Install ripit, lame and eject
You will need a few utilities to do the CD ripping.

sudo apt-get install ripit lame eject

Download my program
You can download my program from github, github.com/martinohanlon/AutoRipper.git

sudo apt-get install git-core
cd ~
git clone https://github.com/martinohanlon/AutoRipper.git

Run it
You have to pass the program 2 parameters:
  • an output directory of where the CD's will be ripped to
  • a timeout in seconds which is the amount of time the program will wait in between CD rips before it quits
cd AutoRipper
python autoRipper.py /home/pi 60

The code

#!/usr/bin/env python

import subprocess
import time
import pygame
import datetime
import argparse

RIPITDIRTEMPLATE = "'\"/$artist/$album\"'"

class AutoRipper():
    def __init__(self,cdDrive,outputPath,timeout):
        self.cdDrive = cdDrive
        self.outputPath = outputPath
        self.timeout = timeout
        self.cdDrive.init()

    def start(self):
        print "AutoRipper - Waiting for Audio Disk"
        #loop until a disk hasnt been inserted within the timeout
        lastTimeDiskFound = datetime.datetime.now()
        while (lastTimeDiskFound + datetime.timedelta(0,self.timeout)) > datetime.datetime.now():
            #is there a disk in the drive?
            if self.cdDrive.get_empty() == False:
                # Disk found
                # is it an audio cd?
                if self.cdDrive.get_track_audio(0) == True:
                    print "AutoRipper - Audio disk found, starting ripit."
                    #run ripit
                    # getting subprocess to run ripit was difficult
                    #  due to the quotes in the --dirtemplate option
                    #   this works though!
                    ripit = subprocess.Popen("ripit --outputdir " + self.outputPath + " --dirtemplate=" + RIPITDIRTEMPLATE + " --nointeraction", shell=True)
                    ripit.communicate()
                    # rip complete - eject disk
                    print "AutoRipper - rip complete, ejecting"
                    # use eject command rather than pygame.cd.eject as I had problems with my drive
                    subprocess.call(["eject"])
                else:
                    print "AutoRipper - Disk inserted isnt an audio disk."
                    subprocess.call(["eject"])
                lastTimeDiskFound = datetime.datetime.now()
                print "AutoRipper - Waiting for disk"
            else:
                # No disk - eject the tray
                subprocess.call(["eject"])
            # wait for a bit, before checking if there is a disk
            time.sleep(5)

        # timed out, a disk wasnt inserted
        print "AutoRipper - timed out waiting for a disk, quitting"
        # close the drawer
        subprocess.call(["eject", "-t"])
        #finished - cleanup
        self.cdDrive.quit()

if __name__ == "__main__":

    print "StuffAboutCode.com Raspberry Pi Auto CD Ripper"

    #Command line options
    parser = argparse.ArgumentParser(description="Auto CD Ripper")
    parser.add_argument("outputPath", help="The location to rip the CD to")
    parser.add_argument("timeout", help="The number of seconds to wait for the next CD")
    args = parser.parse_args()

    #Initialize the CDROM device
    pygame.cdrom.init()

    # make sure we can find a drive
    if pygame.cdrom.get_count() == 0:
        print "AutoRipper - No drives found!"
    elif pygame.cdrom.get_count() > 1:
        print "AutoRipper - More than 1 drive found - this isnt supported - sorry!"
    elif pygame.cdrom.get_count() == 1:
        print "AutoRipper - Drive found - Starting"
        autoRipper = AutoRipper(pygame.cdrom.CD(0),args.outputPath,int(args.timeout))
        autoRipper.start()

    #clean up

    pygame.cdrom.quit()

I had problems with my cd drive where occasionally it wouldn't open the drawer with the button on the front, if you have the same, just type the command eject and it'll pop right out.

38 comments:

  1. apt-cache show abcde

    ReplyDelete
  2. How hard would it be to run something like makeMKV with this?

    ReplyDelete
    Replies
    1. I don't think it would be at all

      Delete
    2. This should get you started https://github.com/JasonMillward/makeMKV-Autoripper

      Delete
  3. Great tutorial! I think ill try to adapt this for burning cd's for audiobooks :)

    ReplyDelete
  4. Hi,

    Thanks for your job. It's very appreciated. The script worked well on my desktop under Ubuntu.

    I needed to install this package too: python-pygame. I thought you should add this on your tutorial.

    Can you tell us what's your CD drive and your usb hub? Witch model?

    (apologize for my english)

    ReplyDelete
    Replies
    1. Hi,

      I didn't include pygame as the package is included by default on raspbian.

      The usb drive I use is a LITEON one, but any usb one should work. Mine has its own power supply so I didnt need a usb hub.

      Mart

      Delete
  5. Hi,
    I've tried using this script. It works for a while, allowing me to rip a few CDs, but then I start getting this error:


    StuffAboutCode.com Raspberry Pi Auto CD Ripper
    AutoRipper - Drive found - Starting
    AutoRipper - Waiting for Audio Disk
    Traceback (most recent call last):
    File "./autorip.py", line 78, in
    autoRipper.start()
    File "./autorip.py", line 27, in start
    if self.cdDrive.get_track_audio(0) == True:
    IndexError: Invalid track number


    I completely reinstalled raspberian, and the CD started working again. But after a few disks, I started getting the same error.

    Any ideas please?

    ReplyDelete
    Replies
    1. Did it fail when trying to rip the same disk? The error suggests something wierd with the disk, like it doesnt recognise it as a CD.

      Delete
  6. Hi, nice coding - worked first time after installing, but may I ask a question? This is in no way a criticism, as I'm new to the RPi, so it's difficult for me to make comparisons, but It seemed to take a long time to rip individual tracks of a CD - typically 3 to 5 mins per track (at 128kbps). Is this to be expected? I'm using raspbian, BTW, with a 32GB SD card. It would obviously be a lot quicker to rip a whole audio CD in Windows on a PC using Media Player, copy to a memory stick and copy the tracks to the SD card. Of course, this pre-supposes one has a PC to hand!

    Kind Regards

    ReplyDelete
    Replies
    1. I dont remember my Pi taking 3-5 mins per track, maybe 1-2 mins, but yes compared to a PC it is going to be much slower. The reason I created this was that I didnt want to sit in front of a PC putting CDs in and then copy them to my NAS.

      I put my Pi in front of the TV, watched a movie and just let it rip, when it finished it popped out the drawer and I put another CD in.

      Delete
  7. Hi, nice coding! I just wanted to ask,if there is any posibility to encode the mp3s in a higher Bitrate. For example 256kbps instead of 128kbps.

    Greetings from Germany

    ReplyDelete
    Replies
    1. Just add the --bitrate option to the ripit command. You can see all the options for ripit on the man page http://manpages.ubuntu.com/manpages/lucid/man1/ripit.1.html

      Delete
    2. Thanks :) everything is working (after some trying around)

      Delete
    3. Ja Alex, du bist Deusch :)?

      Delete
  8. Awesome project! Finally getting around to ripping my girlfriend's music collection. I always just seemed like a hassle before this :D my only question is, how do I tell your script to rip to FLAC format, as I know it's an option in ripit. Is there a parameter I can feed your script to prefer FLAC over MP3? And it will still do the lookups and try to et metadata etc?

    ReplyDelete
    Replies
    1. The ripit readme (http://suwald.com/ripit/README) suggests you can set a different encoder using the --coder option, with 2 being flac. So just --coder 2 to the following line of code, presuming flac is installed and ready to go!


      addripit = subprocess.Popen("ripit --coder 2 --outputdir " + self.outputPath + " --dirtemplate=" + RIPITDIRTEMPLATE + " --nointeraction", shell=True)

      Delete
  9. What does the ++ mean in the encode bar?

    ReplyDelete
  10. I was mistaken(its the ripping progress not encoding).

    When it rips the song, it displays a > that goes across the screen to show progress. Sometimes in there, I see "+" or a "V". Example: | +++ v + + > |

    Does that represent issues reading the disc?


    ReplyDelete
    Replies
    1. Absolutely no idea. Perhaps have a look at the ripit documentation and see if it gives any clues. use man ripit

      Delete
  11. This is a really nice project which I am incorporating into the volumio build. I am by no means a programmer but would like to ask where the autoripper.py program files is installed so I can add a menu command to the Volumio interface

    ReplyDelete
    Replies
    1. If you follow the instructions in the post above the AutoRipper program will be installed at:

      /home/pi/AutoRipper/autoRipper.py

      Delete
    2. Doh, thanks for the reply Martin

      Delete
  12. Hey Martin, excellent code! I'm new to programming and I wanted to know how it would be possible to rip multiple disks at the same time. I noticed the lines

    elif pygame.cdrom.get_count() > 1:
    print "AutoRipper - More than 1 drive found - this isnt supported - sorry!"

    which made me wonder why that is. where is the limitation?

    Thanks!

    ReplyDelete
    Replies
    1. If you have more than 1 drive I see no reason why not, but you will have to make a small change to the program to pass the right cd drive number to the program, because as you have seen I coded it to just pick the first cd drive it found and raise an error if there was more than one.

      Delete
  13. is there a way to keep this running? if no disk in x amount of time, close the tray, but when the tray is manually opened and a disk inserted it detects the new disk and continues on? I don't want to have to ssh in to the pi to start ripping again. Would like to just drop in disk and when it's popped back out it's done :)

    ReplyDelete
  14. This script is amazing!
    One problem though: When a CD is not found by freedb, the tracks are ripped as "Unknown artist" - "Unknwown album", etc.
    When more than one unknown CD is ripped in a session, does the second one overwrite the first one?
    Is there any way to get an interaction with freedb in such cases (e.g. a search)?

    ReplyDelete
    Replies
    1. I have no idea what would happen if there are 2 unknown CDs. In the time I used this to rip 400 CDs I never came across 1 it didn't know. Gut feel say ripit would create a unknown.1 or similar. Perhaps try it.

      Delete
  15. Thank you Martin for a fine tutorial, and everyone for their comments and suggestions. I have this encoding FLAC on a Pi 3. Lots of fun.

    ReplyDelete
  16. It seems to work well, but I get a dialog box 'Removable medium is inserted' each time I insert a CD. I have to press Cancel each time to make it go away. As OS I use most dist-updated Raspbian Jessie (PIXEL).

    ReplyDelete
  17. I got an error because my CD-player doesn't have a tray -- only a slit. It can eject, but it can't close the tray.
    AutoRipper - rip complete, ejecting
    AutoRipper - Waiting for disk
    AutoRipper - timed out waiting for a disk, quitting
    eject: CD-ROM tray close command failed: Input/output error

    ReplyDelete
  18. The playlists that are created (m3u) contain absolute paths. This is a disadvantage since you can't move the folders without the playlists stop working.

    ReplyDelete
    Replies
    1. That will be a feature of ripit rather than the AutoRipper program so Im not sure what I can do about that.

      Delete
    2. I discovered in the ripit manual that '--playlist 2' gives filenames without full path. I also need to use '--chars NTFS' because I save on NTFS drive.

      Delete
    3. Awesome, just change:

      ripit = subprocess.Popen("ripit --outputdir " + self.outputPath + " --dirtemplate=" + RIPITDIRTEMPLATE + " --nointeraction", shell=True)

      and you should be good :)

      Delete

Note: only a member of this blog may post a comment.