Introduction: Delete Those Unwanted ITunes Songs From Your Computer

About: When life gives me lemons, I make batteries. Check out my website at http://nbitwonder.com

Hey there ladies and gentlemen, this is my first coding instructable, so please, when commenting, leave your guns at home (knives are acceptable, however).

With the advent of mp3 players, it has been made possible for people to carry unprecedented amounts of music around with them in their pockets. It is not uncommon to hear of people having collections of 5, 10, even 15 thousand songs (and some people, even more). Of the mp3 players, none is more popular than Apple's iPod and the software that runs it, iTunes.

With such large music libraries, however, it can become somewhat tedious to get rid of the songs that simply don't sound good or appealing to you anymore. It can take ages to go track by track through your music collection in iTunes and get rid of the songs you don't like.

That's where this instructable comes in. In this instructable, I will show you a way to automatically get rid of songs that are simply just plain awful, with some assistance from my good friend, Perl.

A good basic knowledge of Perl is extremely helpful when attempting to repeat this instructable, though not required (you could certainly just take the finished code product and copy/paste it and it will work just fine). For those interested in Perl, I highly recommend the book "Learning Perl" from O'Reilly, it's a good read and one of the best written books on Perl I have had the chance to read.

IMPORTANT: While the methods presented here will work if done correctly, I cannot and simply will not accept any responsibility if you do something stupid and delete your precious songs. Please strongly consider backing up your music files before attempting to write or execute a script such as the following. Just err on the side of caution so that we don't have any feelings hurt if you don't, thanks.

Read the warning? (Yes, even you, person who didn't read the warning) Great! Let's get started :-)

Update: It seems some perl scripters have been hard at work writing up iTunes scripts. For a variety of iTunes-related scripts, check out Teridon's Scripts.

Step 1: Getting Started

As with all things, there is a little preparation that needs to be done before we can run this script. So, things you will need to automatically delete songs from iTunes:
1) A computer (duh)
2) iTunes (double duh)
3) Perl (hard to run a Perl script without Perl, wouldn't you say?)
4) Your favorite editor (I'm a proponent of vi and vim myself, but any text editor should do the trick)
5) An iPod (not mandatory, but nice to have for reasons that will be made clear soon enough)

Getting Perl: Follow the instructions here, should be simple enough:
http://www.perl.com/download.csp

Once you have Perl downloaded, installed, and ready to go, we'll want some background information on what it is we're doing. It should be noted that the script was originally developed for Mac OS X, although the same methods should work on non-Unix based OSes such as Windows. Time for the boring, er, "educational" part of the Instructable.

Step 2: The Basic Script Idea

iTunes includes a 5-star rating system to allow users to rate songs. Songs with a 5-star rating are considered to be the best songs while songs with a 1-star rating are assumed to be some of the worst songs. For our purposes, we will assume that any song that lacks a rating (aka 0 stars) is one that the user has not had a chance to rate yet.

Personally, when rating songs, if a song has a 1-star rating, I find it is probably bad enough that it doesn't deserve the precious hard drive space that the song takes up. Therefore, the Perl Script I will present to you in this Instructable will parse through the iTunes Library and delete any track that has been assigned a 1-star rating.

As an added bonus, the songs can be rated while the user is on the go using an iPod. This way, you can select songs for deletion while on the go simply by rating them, and they will be automatically deleted when you sync your iPod to your computer later (if you automatically sync your iPod to your computer).

Now that we know what we're doing, let's see how we're going to pull song information from iTunes for the Perl Script.

Step 3: The XML Library: ITunes' Goto Guy

In order to delete a song from the computer based on it's rating, we require 2 pieces of information: the song's rating and the location of the song. Fortunately for us there is a handy file that we can get all of the information we ever wanted about iTunes from: the iTunes Music Library XML file. The XML file is called "iTunes Music Library.xml" and should be located in your Music directory on your main Hard Drive.

About the XML file: The iTunes xml file is a sort of database maintained by iTunes, and always kept up to date. When a modification is made in iTunes, the corresponding portion of the iTunes XML file is modified to note this change. A sample entry from my iTunes XML file is given below:

1218

Track ID1218
NameTake On Me
ArtistA-Ha
Genre80's
KindMPEG audio file
Size3682382
Total Time230138
Date Modified2007-09-24T02:11:30Z
Date Added2008-05-28T05:00:24Z
Bit Rate128
Sample Rate44100
Play Count2
Play Date3297176818
Play Date UTC2008-06-25T01:26:58Z
Rating40
Album Rating40
Album Rating Computed
Persistent ID9AC5DB9713240B44
Track TypeFile
Locationfile://localhost/Volumes/HD1/iTunes%20Music/A-Ha/Unknown%20Album/Take%20On%20Me.mp3
File Folder Count4
Library Folder Count1


As can be seen from the above entry, the XML file consists of values surrounded by HTML-style tags. For the purposes of the script, we will be interested in the Track ID, Name, Artist, Rating, and Location bits of information.

If you are looking at the XML file, you may notice from above that this song has a "rating" of 40. iTunes assigns each song an integer, ranging from 0 to 100, with every 20 points being an additional star for the rating. So, a rating of 20 corresponds to a 1 star rating, a rating of 40 is a 2-star rating, and so on, with 100 being a 5-star rating.

So now that we know about the XML file, let's start scripting

Step 4: Your Friend the Hash (even If You Aren't in Amsterdam)

Before we can eliminate files, we need a working database that relates all of the song information together. While more sophisticated data structures, such as an array of hashes or hash of hashes, could be used, this script is simple enough to merit the use of a simple list of hashes.

For the uninitiated, a hash is nothing more than an array which is indexed by strings, or a jumbled mass of key/value pairs. One can think of a hash as a large barrel with stuff (the values) in it, and everything in the metaphorical barrel has a tag (the keys) attached. You can pull any item in the barrel out simply by finding its tag.

For more information on hashes, the following link may be useful: http://www.tutorialspoint.com/perl/perl_hashes.htm

We can use the information that every song in the iTunes XML file is assigned a unique track ID to keep track of the songs. Because of this, the trackID makes an ideal key for the hashes. Therefore, we can set up 4 hashes for song title, artist, rating, and location. Once these have been established, we can scan through the file and fill our hash database using some simple regular expressions, which will be shown next.

Step 5: A (very) Brief Lesson on Regular Expressions

In order to grab our hash entries from the XML file, and also save a little time searching, we are going to want to call upon the help of one of Perl's more powerful features: the regular expression. I will do a very brief lesson on regular expressions, but for those of you that want more detailed explanations, there are lots of good regular expression tutorials online. Check out http://perldoc.perl.org/perlretut.html for a good regular expression tutorial.

In short, regular expressions provide us with a set of tools to go through strings, replacing one string with another string, or saving portions of a string for later use. Regular expressions provide 2 functions, the matching (m//) and replacement (s//) operators, that are going to be used for this project. To use either of this functions in an assignment context, we will use Perl's binding operator (=~), which is used to bind a pattern to a string variable of choice.

Examples:
$comment =~ /Purduecer/;  #returns true if $comment string contains phrase "Purduecer"s/[a-z]/[A-Z]/;           #take all lowercase letters in string and capitalize them in $_ string
A second useful feature of regular expressions is that of memory variables. In regular expressions, you can place certain items in parenthesis, and then use the special pattern match memory variables $1, $2, etc. to access the portions of the strings that matched these parts later.

Examples:
/(Instructables) Robot/;    #Match any line with the phrase "Instructables Robot"$website = $1; #Save result of successful pattern match contained in first set of parens               #(in this case, variable $1 contains "Instructables")
Finally, in regular expressions, there are certain characters that serve a special purpose. These are backslash escapes (all of which should look familiar to C programmers), character classes and metacharacters. For example, to match any single letter that has a tab on either side, we could say:
/\t[a-zA-Z]\t/
Those square brackets are used in regular expressions to define a character class. Suppose, however, that we wanted to find text enclosed in square brackets in the line. We could not simply write it as is, as is shown below:
/[[a-zA-Z]+]/;     #WRONG, outer square brackets interpreted as a character class, do not do this
So, to remove the special qualities of the pair of square brackets, add a backslash beforehand, which removes the special qualities of any otherwise reserved character within a regular expression.
/\[[a-zA-Z]+\]/;   #Correct, now matches any number of letters enclosed within a pair of square                   #brackets

Step 6: Applying Regular Expressions to the ITunes Script

Now that we hopefully have some inkling on the basic underlying concepts powering regular expressions, it's time to apply these to the iTunes script.

When reading xml code, certain characters, such as the forward slash, are encountered fairly often. Therefore, we will take advantage of the fact that the m// operator let's you choose whatever delimiters you want in the code (we will use square brackets, though other delimiters will certainly work)

First off, we only need to read a portion of the full iTunes XML file. Parts of the file containing information like playlists, etc. are not necessary. The first line of the playlists section, which comes after the song information, looks like the following:
<key>Playlists</key>

Therefore, in a while loop, we can add in a statement that jumps to the end of the file reading if that line is encountered.
while(<XML_FILE>) {    #loop_instructions_here    last if($_ =~ m[<key>Playlists</key>])}
Next, to build the hash IDs, we can use an if-elsif tree to build our database hashes, using the memory match variables we learned about in the previous step to save values into the hashes:
if($_ =~ m[<key>(\d+)</key>]) {    $id = $1;  } elsif($_ =~ m[<key>Rating</key><integer>(\d+)</integer>]) {    $rate_hash{$id} = $1;  } elsif($_ =~ m[<key>Name</key><string>(.+)</string>]) {    $name_hash{$id} = $1;  } elsif($_ =~ m[<key>Artist</key><string>(.+)</string>]) {    $art_hash{$id} = $1;  } elsif($_ =~ m[<key>Location</key><string>file://localhost(.*)</string>]) {    $loc_hash{$id} = $1;  }
Now that we have constructed the basis of our hash database, we will cover locating and removing 1-Star files, so onward to the next step!

Step 7: Locating and Deleting Those 1-Star Tracks

Now that we have our has database set up, it's time to hunt for the 1-star tracks, so that we can delete them. Perl provides a useful looping construct called a foreach loop that can be used to iterate over all of the keys of our hashes.

It should be noted that not every track in your iTunes library will have an entry in the ratings hash. This is because songs that don't have a rating aren't given a track rating line in the iTunes XML file. Therefore, when looping through the hashes, we will want to use the following construct:
foreach $id (sort keys %rate_hash) {  #...insert looping code here}
After that, it's as simple as using the following statement:
unlink $loc_hash{$id} if $rate_hash{$id} == 20;
The unlink function used in the sample code above is Perl's way of deleting files. You may be tempted to use a system call to your operating system's delete function, but for portability reasons the code I have written doesn't use the system call.

We now have the basic structure for our code. In the following steps, we will make some refinements to the code so that you can have a full-featured, functional script to get rid of those pesky 1-stars.

Step 8: URI Escapes and How to Get Around Them

If you attempt to run the script as is, you will run into numerous errors from your operating system, and if you look at the file paths you're trying to delete, you may find unusual characters embedded in the pathways that are undesirable. There's a pretty good chance that these paths contain URI escape characters.

What are URI escapes? In HTML and XML, URI escape characters (aka URI escapes) are special metacharacters used to search for literal instances of that character. For example, you will never see a simple whitespace in an XML character string. You will, however, see the XML representation of a simple whitespace, %20. (Surely you've seen these in the URL bar in your browser and wondered what they were. Well, now you know) A URI escape consists of a % sign followed by a 2-digit hexadecimal code representing the character's ASCII value.

At this point, you could write a whole bunch of replace statements on the location hash to replace every possible metacharacter that you encounter (i.e. $loc_hash{$id} =~ s/%20/ /; and so on). Fortunately, though, Perl provides a better way to take care of these things.

Perl comes bundled with a module called URI::Escape, which has a built-in function, uri_unescape, that will do the job of elliminating the URI escapes for us. To use the module, simply add the following line at the top of your code:
                         use URI::Escape;
Now, after we assign the $loc_hash{$id} array in the XML file loop, we can add the following line afterwards:
                        uri_unescape($loc_hash{$id});
The above line automatically elliminates any URI escape characters. As an added bonus, it translates some international characters as well. I ran the script with a few filepaths that had Japanese konji in them, and the uri_unescape function translated the XML formats of those characters to their original characters so that unlink could successfully delete the file.

Before we move on, one additional step must be taken. For reasons that I don't fully understand, iTunes encodes the ampersand(&) symbol as & in it's strings. If anyone knows why this is, please let me know. In the meantime, we elliminate this issue by adding the following line under the uri_unescape line:
                     $loc_hash{$id} =~ s/&#/&/;
Now the script will have filepaths clear of strange characters, and the unlink function will be able to find files in their proper locations to delete them. Well, it will if you are using Mac OSX (and presumably most other Unix-based OSes as well). Read on for making the script compatible on other operating systems (namely, Windows).

Step 9: Adding Script Compatibility to Windows

The script, as written so far, effectively removes tracks on Unix-based operating systems. To make it work on Windows, one minor addition is needed.

Unlike Mac and other Unix-based OSes, Windows does not support the concept of a single root directory. Therefore, all pathnames start with a volume (C: or E: or any other letter you can think of, for that matter) as opposed to the root directory (/).

Luckily for us, Perl has a special variable, $O (that's a capital o, not a zero), that tells us what operating system we're currently using. So, if the value stored in that variable contains "Win", we're going to want to remove the leading slash on our XML location filepath. This can be done with the following line of code (added after we successfully parse the location of a track from the XML file):
$loc_hash{$id} = substr($loc_hash{$id},1) if $^O =~ /Win/i;
Now, the script will work whether it's run on OS X or Windows.

Step 10: The Completed Script

Attached is my version of the completed iTunes autodeletion script, for those of you who just wanted the end product and not the explanation on how it works.

To use the script you will want to save the file as a perl file (.pl extension) and then make it executable (chmod 751 in Unix, not sure what it is for Windows systems).