Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, PHP, and whatever else I happen to feel like hacking at.

Importing vinyl into iTunes

Jerry Stratton, September 28, 2005

Ripping vinyl takes three basic steps: digitizing the album, splitting the album, and importing the album into iTunes, setting all of the information correctly. The hard part of ripping vinyl, for me, has always been the splitting and the setting information steps.

Splitting has always involved first using Toast’s Spin Doctor to automatically split the album; if that worked, then adjust the edges of both sides of the album and name each piece; if that didn’t work, as it often didn’t on live albums, concept albums, or albums with lots of quiet space within songs, I’d go into a sound editor and eyeball it, chopping off each piece by hand, and doing so from the end to avoid excessive redraw times of the waveform.

Sound Studio Markers

Markers in Sound Studio can be given names, and their times modified.

Recent versions of Felt Tip Software’s Sound Studio have added a new feature: add marker, and split by marker. You can add named markers, and Sound Studio will split the album into pieces, each piece named according to its marker. I don’t even bother using Spin Doctor any more. The markers are easily dragged (a feature I’ve wished Spin Doctor had from the moment I started using it). In times of confusion I can edit the marker’s time, adding the previous known song split’s time to the song time listed on the album to get a ballpark location for the next marker.

That leaves setting the information in iTunes as the remaining time-consuming part. Besides album name, which is pretty easy, if I’m going to want to listen to the album in its original order I have to go through each track and set the track number. If I’m importing several albums at a time, I either have to pick each album’s songs out of a mass of untitled songs, or I need to rip, split, set, rather than just listen to all the albums I want to rip to today, split them all, and then set their information.

Since I’ve just picked up a FireWire AudioPhile, I’ve been ripping a lot of vinyl, and I decided to do something to make this easier. Sound Studio also has the ability to prepend an index number to the beginning of each split piece. I decided to write an AppleScript that would use the index number as a guide for setting the track number.

Now, the index number, for me, isn’t going to be the track number. There is generally empty space at the beginning of side one, the transition from side one to side two when I flip the album over, and at the end of side two. I leave these markers in SoundStudio titled "Marker x". But the index numbers do set an order for the tracks.

Preparing the script

Sound Studio Song Folder

These tracks are ready for import, in order, into iTunes.

What does the script expect to see? If I’m going to simply replace the import portion of the process, the script is going to get a folder, whose name is the album, and which contains all of the split files. The split files will have a number in front of them giving their order, but because some of the files are empty spots between sides or at the beginning and end of the album the number will not be the actual track number.

What will the script do? It needs to import the files, convert them to MP3 (or AAC in my case), set the album name for each song to the name of the folder that once contained it, set the track count to the number of imported songs, and give each track the appropriate track number.

It turns out that iTunes has a single function that both converts and imports. (You can see the equivalent menu item in iTunes’ advanced menu if you hold down the option key while pulling down the menu.)

I considered putting the album folder(s) inside of an artist folder so as to be able to “automatically” set the artist, but that’s just as much work or more as just selecting all of the albums for that artist and setting it within iTunes by hand.

The functions

Now that I know what the script is going to do, it is time to write each part. This script consists of three functions: the “on open” handler that accepts items dropped onto it from the Finder, the “importAlbum” handler that takes a folder and imports the valid files within it, and the “getSongTitle” handler that takes a song’s filename and turns it into the song’s title, returning false if the file is not one it should try to import.

Properties

Sound Studio gives all index numbers the same length. If the split file is one of the first 99, its index number will be two digits long. For files after the first 99, it will be the size of the number. Sound Studio itself is very scriptable, so you can verify this by writing a split script:

[toggle code]

  • tell application "Sound Studio"
    • tell document 1
      • set markerInterval to sample count / 120
      • set markerTime to 0
      • repeat while markerTime is less than sample count
        • make new marker with properties {position:markerTime}
        • set markerTime to markerTime + markerInterval
      • end repeat
    • end tell
  • end tell

Because I want to detect empty markers, but I don’t want to throw out valid files that might have a similar name, I’ll set the script to only throw out Marker-named files that have a limited number of digits. A maxCounterSize property of 2 will throw out any Marker-named files with two digits or less. If you have albums with a hundred or more files, you’ll want to increase this to three.

Sound Studio Timeline

Markers may also be adjusted in the timeline.

Because Sound Studio might later change the default marker name, and because I know I will forget to change the length values when that happens, I’ve also made a blankName property. This contains the name that blank spaces (the space between sides, for example) will contain--mainly because if the marker doesn’t represent a song, I won’t rename the marker.

  • --how many digits can a counter contain?
  • property maxCounterSize: 2
  • property blankName: "Marker"

On Open theFolders

When the finder hands off all of the items I’ve dropped onto the script, the script will loop through each item. When it finds a folder, it will hand that folder off to the import album script.

As the script currently stands, it throws up a dialog box when it encounters an item that is not a folder. This most likely represents a mistake on the part of the person requesting the import.

On importAlbum from theFolder

The meat of this script is handled by the importAlbum handler. This handler takes a folder and requests, from the Finder, a list of all files in that folder. It then asks the Finder to sort the list by the name of the files.

Once it has a list of files in the correct order, it goes through each item in the list, gets the song’s title, converts the song to the currently chosen encoding method, and sets the known track information. Because the script might take a few minutes to complete, I have it speak its progress. At each file, it speaks when it begins importing a track or it skips a track.

The heart of this handler is the convert function in iTunes. Convert converts a folder or a file into the chosen encoding (generally MP3 or AAC), and returns a list of all of the imported tracks. Because the script is importing one at a time in order to get rid of empty Markers and ensure the order of the tracks, it only needs the first item of the list returned by convert. (If I really wanted to be paranoid, the script probably should verify that the list really does only contain one item.) The script could just as well have imported each folder, and then gone through each item in the list throwing out the empty markers. It’s really a matter of style. For example, if you didn’t have to worry about empty parts:

[toggle code]

  • tell application "iTunes"
    • set theSongs to convert theFolder
    • --would need to put the songs into track order here
    • set trackNumber to 0
    • set trackTotal to the count of theSongs
    • repeat with theSong in theSongs
      • set trackNumber to trackNumber + 1
      • set the album of theSong to theAlbum
      • set the track number of theSong to trackNumber
      • set the track count of theSong to trackTotal
    • end repeat
  • end tell

On getSongTitle from songFileName

As noted above, some of the files in the folder won’t be files I want to import. Further, because the song files have an index in front of them (to designate the track order) and an extension on the end (in my case, I have Sound Studio save the files as .aif files), the file name is not the song title.

The getSongTitle handler does double duty: it determines the song’s real name from the song’s file name, and it determines if the file should be imported or ignored. As it stands, the script does three checks to determine the validity of a file:

  1. Does the file name contain a space? All valid filenames will contain a space because there is a space between the track number and the song’s name.
  2. Does the first space occur after no more than maxCounterSize characters? If it occurs after that, then whatever the file is, it doesn’t have an index number on it.
  3. Does the song name portion of the filename begin with the blankName property, and if so does the rest of the song name portion look like an index number?

If any of those conditions occur, this is probably not a song file. The handler returns false rather than a song title.

If this does look like a song title, the script gets the song title by looking for the position of the first space and the position of the final period. If there is no period in the file name, the script “assumes” that the file does not have an extension, and doesn’t worry about it. Note that this does mean that the script will fail if a song title contains a period but does not have an extension.

The determination of the final period’s position is a little tricky.

[toggle code]

        • set endOfTitle to the length of songFileName
        • if songFileName contains "." then
          • --get the period's distance from the end by reversing the name
          • set extensionSize to offset of "." in (reverse of characters of songFileName as text)
          • set endOfTitle to endOfTitle - extensionSize
        • end if

If the file name does not contain a period, then the final character in the song title is the final character in the file name. But if it does contain a period, the script needs to get rid of the extension. Song titles can contain periods, so we can’t just get the offset like we did to get the first space. Other scripting languages have special functions for getting the first character from the end of a piece of text but AppleScript does not.

So the script first gets the characters of the file name as a list of characters, reverses that list, and then converts the list back to text. If the file name was “03 O.D.'d on Life Itself.aif” it is now “fia.flestI efiL no d'.D.O 30”. It then gets the offset of the first period in this new, reversed text. In this example, that offset will be four. Subtracting four from the length of the string gets the end of the song’s title in the original file name.

Add Album to iTunes Script

Here is the full script. Save this as an application, and you’ll be able to drag and drop folders onto it. It will try, as described above, to import the files contained in these folders into iTunes.

[toggle code]

  • --how many digits can a counter contain?
  • property maxCounterSize: 2
  • property blankName: "Marker"
  • on open theFolders
    • repeat with theFolder in theFolders
      • --is it a folder?
      • set folderInfo to info for theFolder
      • if the folder of folderInfo then
        • importAlbum from theFolder
      • else
        • set folderName to displayed name of folderInfo as string
        • display dialog folderName & " is not a folder."
      • end if
    • end repeat
  • end open
  • on importAlbum from theFolder
    • --get the album name from the folder name
    • --and get the sorted list of items in the folder
    • tell application "Finder"
      • set albumName to the name of theFolder
      • set theSongs to every item of theFolder
      • set theSongs to sort theSongs by name
    • end tell
    • --give us the option of canceling, but start importing after 15 seconds
    • display dialog "Importing " & albumName giving up after 15
    • set newTracks to {}
    • set trackNumber to 0
    • repeat with theSong in theSongs
      • set songName to the name of theSong
      • set songFile to theSong as text
      • set songTitle to getSongTitle from songName
      • if songTitle is not false then
        • set trackNumber to trackNumber + 1
        • say "Importing song " & trackNumber & ": " & songTitle
        • tell application "iTunes"
          • set newTrack to item 1 of (convert songFile)
          • --set the track's information
          • set newTrack's name to songTitle
          • set newTrack's album to albumName
          • set newTrack's track number to trackNumber
        • end tell
        • --store for setting the track count when we know what it is
        • copy newTrack to end of newTracks
      • else
        • say "Skipping file " & songName
        • delay 1
      • end if
    • end repeat
    • --set track count of all songs of album
    • repeat with theTrack in newTracks
      • tell application "iTunes" to set theTrack's track count to trackNumber
    • end repeat
    • say "Album " & albumName & " completed."
  • end importAlbum
  • --gets the song name from a SoundStudio-created sound file
  • --returns false if it detects that this is an invalid file:
  • -- the file was not created with prepended number
  • -- the file is called blankName & " xx"
  • on getSongTitle from songFileName
    • set songTitle to false
    • if songFileName contains space then
      • set startOfTitle to (offset of space in songFileName) + 1
      • if startOfTitle is less than or equal to maxCounterSize + 2 then
        • set endOfTitle to the length of songFileName
        • if songFileName contains "." then
          • --get the period's distance from the end by reversing the name
          • set extensionSize to offset of "." in (reverse of characters of songFileName as text)
          • set endOfTitle to endOfTitle - extensionSize
        • end if
        • set possibleTitle to characters startOfTitle through endOfTitle of songFileName as text
        • --look for reasons not to use this file
        • if possibleTitle starts with blankName & space and length of possibleTitle is less than or equal to length of blankName + 1 + maxCounterSize then
          • say possibleTitle & " is blank space."
          • delay 1
        • else
          • set songTitle to possibleTitle
        • end if
      • end if
    • end if
    • return songTitle
  • end getSongTitle
May 7, 2006: Run a script on inserting an audio CD

Preflighting vinyl import was pretty easy, because for vinyl import, I was already using an AppleScript to do the import. However, I have the same problem when importing CDs: if I recently converted a podcast to MP3 mono and forgot to change it back, I’ll end up accidentally importing CDs as low quality mono rather than high quality stereo.

It isn’t that big of a deal, because iTunes will recognize when you re-import a CD you’ve imported before. It will ask if you want to replace the existing files, and if you say yes, it will copy all of the information over to the new, better files: play count, last played, names, ratings, even album cover if you’ve put one onto the tracks.

But it would be nice to get a warning when I’m about to import an audio CD at poor quality similar to the warning I get when I’m about to import vinyl at poor quality. Fortunately, there is an easy way of running our own script whenever we insert an audio CD.

Back when you first got your Macintosh, you might remember that sometimes, on inserting media, the Mac would ask you what you want to do with that media. The Mac then stores your preference for future use. You can reset these preferences in your System Preferences. Choose “CDs & DVDs” from the Hardware row. You can choose one of your scripts for what to do “When you insert a music CD”.

The script must be saved as File Format script, not as an application.

[toggle code]

  • on run
    • tell application "iTunes" to set theEncoder to the format of the current encoder
    • if theEncoder is not "AAC" then
      • display dialog "iTunes is not set to use AAC"
      • return
    • end if
  • end run

Obviously, if you prefer MP3 you’ll change that as your encoding format. Unfortunately, I can still find no way to check the bit rate.

October 12, 2005: Preflight iTunes Imports

So, uh, once you are using an AppleScript to import your music automatically, you can also add checks to make sure that iTunes is doing what you expect. For example, in looking over the new iTunes 6 to see if it has any obvious new functions, I noticed that my encoder was set to MP3 “good” (128 kbps) rather than my preferred encoder, AAC.

While experimenting with iTunes some time back, I set the encoder to MP3 and then forgot to switch it back. Last weekend’s albums were all imported as MP3 instead of AAC (and at 128 kbps instead of my preferred 192 kbps for MP3s). This isn’t nearly as bad as the time I experimented with voice encoding and set it down to 96 kbps mono several years ago, but it is something that is very easily preflighted in an AppleScript.

[toggle code]

  • on open theFolders
    • tell application "iTunes" to set theEncoder to the format of the current encoder
    • if theEncoder is not "AAC" then
      • display dialog "iTunes is not set to use AAC"
      • return
    • end if
    • --the rest of the import goes here
  • end open

This checks to ensure that the current encoder is what I want it to be. You of course will want to switch that to your preferred encoder.

Now, that does not check that the bit rate is okay. There is no way currently in iTunes (that I can find) to get the bit rate of the current encoder. But we can check the bit rate of the imported tracks after we import them. In the “on importAlbum” handler, add to the section where we set information:

[toggle code]

  • tell application "iTunes"
    • set newTrack to item 1 of (convert songFile)
    • --set the track's information
    • set newTrack's name to songTitle
    • set newTrack's album to albumName
    • set newTrack's track number to trackNumber
    • --get any information we need
    • set newBitRate to newTrack's bit rate
  • end tell
  • if newBitRate is not 128 then
    • display dialog "The bit rate is not 128. You might want to cancel."
  • end if

Replace 128 with your preferred bit rate. Preflighting and post-flighting your AppleScript workflows is always a great idea. This is one area where automating tasks can save a lot of time that would be taken up having to redo several tasks. When you have an automated task, you should only ever make a mistake once. After you make a mistake, you can add that to your list of things to automatically check for.

  1. <- One-fisted tabs
  2. Disabling Quit ->