This little tutorial means to further acquaint you with Python, its data science libraries, and Jupyter Notebook. It is also keen on better understanding one of the data formats that likely comprise a significant part of your future: .XML

At the same time, it tries to contextualize all of this work with how data science actually gets done, and what it can look like en route. This is a lot to take in all at once, I know. But, in my small opinion, you cannot separate lessons in doing data science from lessons in programming from a working knowledge of where and how our data lives. These three components have to inform one another, or there is no point going forward.

I suggest that you work along with this Jupyter Notebook tutorial by opening up your own notebook (with a Python 3 kernal) and building each section as you make sense of it. Don't hesitate to break things, add things, go off in your own direction. Remember: This is the advantage of the digital Notebook format. Don't get stuck in pen-and-ink-think.

ONE

You're going to need an iTunes .plist (preferences list) file for this exercise. Its actually a version of an .XML file. In case you don't have one handy, or if you'd prefer to work along with the same one I'm using, I happen to have a copy of it right here for download.

download drm_itunes.xml

Like most .xml files, this is just a flatfile: It doesn't actually contain any of the music to which it refers.

TWO

This week, Jupyter Notebook is again our weapon of choice.

THREE

Data Handling

Don't forget: Never work on the original copy of a data file. You will regret that decision. If you're using your own music.xml file, make a duplicate, leaving the original untouched and unmoved.

Rename your copy of the file in order to mark it as a disposable working copy: Say, myiTunesData.xml. Make note of that in your processing book. (These ostensibly disposable copies really add up over time. I found yesterday that I had 4 copies of the (awesome) NYC TaxiCab GPS dataset (January 2016 edition); each copy is just over 2GB in size.)

Now from Mac's Finder or Window's Explorer (or the command line, if you're feeling adventuresome), move your .xml datafile into the same directory that now contains your Jupyter file. In my case, for example, .iTunesPlotter.ipynb and itunes_gwl.xml are both safely stored in the same directory:

Users/garrisonlemasters/Documents/AnacondaProjects/iTunes/

The .xml file does not have to be in the same directory as the Jupyter notebook (the .ipynb file), but it makes life much easier.

Q: Doesn't all of this seem like a lot of administrative work?

A: It probably only seems that way because it is a lot of administrative work. Doing data science means learning to love file versioning systems, duplication macros, RAID arrays and redundant storage systems. It means you fantasize about off-site storage and data firehoses. Do a good job keeping track of your data and the rest will follow.

FOUR

Let's look at the file we want to ingest and manipulate. As we can tell from the file extension and from the file header, it is an .XML file. Here's the header:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">

One of the principles to which .XML files adhere is that their headers point to a place on the web where someone maintains the file's schema: That is, a detailed description of how the file is organized. That's the second line (above), the one that begins !DOCTYPE and ends with the standard file extension for XML schema, .dtd.

Apple -- like many companies -- uses the .XML standard, but doesn't always adhere to the standards themselves. This is usually annoying. In this case, it means that the file is easier for us to interpret, but less easily interpreted by Python. To understand why, let's talk about dictionaries.

In computation, a DICTIONARY is a fairly simple way of storing information. It is called a dictionary because it roughly mimics the way a dictionary stores data: With a unique term on the left-hand side of the page and one or more definitions on the right-hand side. When we consult a dictionary, we always know the term that we're looking for:

Doing extensive work with an unfamiliar library, data format, or hardware? Keep a notebook or a webpage with all of the relevant links to tutorials, src code, and (ideally) the developers' docs. For decades, documentation of code has been abysmal: But it has gotten to the point that some documentation is genuinely useful now. Here is one page, for example, that Apple maintains on its use of XML in PLIST formats. And here is a page devoted to the Python Software Foundation's plistlib. The changelog that appears right above the yellow box on that page hints at the terrific difficulties that even the best documentation is hard-pressed to ameliorate: "Changed in version 3.4: New API, old API deprecated. Support for binary format plists added." While the shift from 3.3 to 3.4 doesn't seem like much, the 3.4 library is effectively completely new.

Process

In [1]:
import plistlib
In [2]:
fileName = 'drm_itunes.xml'
In [3]:
myFileObject = open(fileName, 'rb')

This statement opens our file (fileName) as a Read-Only file ('r') in Binary mode ('b'). "Oh no you didn't", you'll complain: "What's this yer on about now, binary? I thought if I could read the file, it was a plaintext file -- and I can bloody well read this .xml file just fine, gov'nuh, I do thank ye very much. It isn't binary!"

Well, yes: How colorful you are! And how right you are!

Or rather, right you were, because at the same time that we opened the file, we were also asking Python to convert it into a binary object right away: The 'b' for Binary didn't reflect the nature of the file -- "Python, open that file, and be careful, its in BINARY!". Instead, it told Python how we want it to treat the data it finds inside: "Python, open that file, and store it as binary data, right away!"

So the obvious question is: Why? Why change the 'stream' of information from a legible (human-readable) stream of letters to a stream of 1's and 0's?

The answer isn't that interesting, in truth: In our case, we do that because the library we're about to use -- Python's plistlib -- wants its data delivered as a binary stream.

And so that is exactly what we've done with our open(filename, 'rb') statement: We've asked Python to take our file full of song titles and singer's names and convert them all to a stream of binary digits. Python obliges by converting the data, and then it packages it up inside an object called an _io.BufferedReader: Basically, a high-tech chrome-and-carbon-fiber box, full of bells and whistles that make it easier for us to get immediate access to lots of data, quickly.

We can get a quick overview of the myFileObject object if we print() it to our screen:

In [4]:
    print(myFileObject)
<_io.BufferedReader name='drm_itunes.xml'>

Our high-tech box, filled with liquid data, is called a BufferedReader.

So: Python knows how to pull data in and push data out (its all 1's and 0's at this point, after all). But we usually need one or more external libraries if we're going to make automated sense of that data at all. In our case, Python already has a plist library (called, appropriately enough, plistlib) ready to go. (Note that there are other libraries that would also work here, like the XML parsing library; but plistlib is custom-made for Apple-style data, which suits us perfectly.)

When we use one of the tools in the library (in this case, load is a 'method' -- an action, a verb -- stored inside our .plistlib), we need to tell Python that we are using a tool from that library. We've done this sort of thing before. In this case, it looks like this:

In [5]:
    songLibraryData = plistlib.load(myFileObject)

Remember: In the code above, songLibraryData and myFileObject are both variables: We want Python to 'read between the lines,' not to interpret those words literally. Because of that, we do NOT use quotes around them. If I put quotes around myFileObject, for example, Python would think that the phrase myFileObject was important. In this case, it clearly is not: The value contained within that variable is what matters.

Python asks the plist library to take a look at that binary stream of data we opened up; if it recognizes how things are organized, we're in luck!

Let's see what we've got: What does songLibraryData look like?

In [6]:
print(songLibraryData)
{'Major Version': 1, 'Minor Version': 1, 'Date': datetime.datetime(2018, 2, 8, 16, 16, 42), 'Application Version': '12.7.3.46', 'Features': 5, 'Show Content Ratings': True, 'Music Folder': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/', 'Library Persistent ID': 'E2C1E44874E953B8', 'Tracks': {'1494': {'Track ID': 1494, 'Name': 'Take on Me', 'Artist': 'NO BS! Brass', 'Album Artist': 'NO BS! Brass', 'Album': 'Alive in Richmond 2.0', 'Genre': 'Jazz', 'Kind': 'Purchased AAC audio file', 'Size': 7379988, 'Total Time': 195440, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 16, 'Track Count': 18, 'Year': 2011, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 36), 'Date Added': datetime.datetime(2014, 9, 22, 19, 17, 57), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 8, 'Play Date': 3541067864, 'Play Date UTC': datetime.datetime(2016, 3, 17, 17, 57, 44), 'Skip Count': 1, 'Skip Date': datetime.datetime(2015, 10, 13, 18, 22, 24), 'Loved': True, 'Normalization': 5314, 'Artwork Count': 1, 'Sort Album': 'Alive in Richmond 2.0', 'Sort Artist': 'NO BS! Brass', 'Sort Name': 'Take on Me', 'Persistent ID': 'E3B56D40EF180A73', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/NO%20BS!%20Brass/Alive%20in%20Richmond%202.0/16%20Take%20on%20Me.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1496': {'Track ID': 1496, 'Name': 'Splatter Splatter', 'Artist': 'Moxy Fruvous', 'Album Artist': 'Moxy Fruvous', 'Composer': 'Unknown', 'Album': 'Thornhill', 'Genre': 'Rock', 'Kind': 'Purchased AAC audio file', 'Size': 6476758, 'Total Time': 179731, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 7, 'Track Count': 12, 'Year': 1999, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 32), 'Date Added': datetime.datetime(2013, 11, 7, 0, 31), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 21, 'Play Date': 3575360579, 'Play Date UTC': datetime.datetime(2017, 4, 18, 15, 42, 59), 'Skip Count': 2, 'Skip Date': datetime.datetime(2015, 9, 21, 0, 28, 30), 'Normalization': 6789, 'Artwork Count': 1, 'Sort Album': 'Thornhill', 'Sort Artist': 'Moxy Fruvous', 'Sort Name': 'Splatter Splatter', 'Persistent ID': '2C75C312BA0835DA', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Moxy%20Fruvous/Thornhill/07%20Splatter%20Splatter.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1498': {'Track ID': 1498, 'Name': 'Journey of the Sorcerer', 'Artist': 'Eagles', 'Album Artist': 'Eagles', 'Album': 'The Studio Albums 1972-1979', 'Genre': 'Rock', 'Kind': 'Purchased AAC audio file', 'Size': 13972779, 'Total Time': 396921, 'Disc Number': 4, 'Disc Count': 6, 'Track Number': 4, 'Track Count': 9, 'Year': 2013, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 52), 'Date Added': datetime.datetime(2013, 10, 31, 9, 33, 54), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 11, 'Play Date': 3594875816, 'Play Date UTC': datetime.datetime(2017, 11, 30, 13, 36, 56), 'Skip Count': 1, 'Normalization': 1270, 'Artwork Count': 1, 'Sort Album': 'Studio Albums 1972-1979', 'Sort Artist': 'Eagles', 'Sort Name': 'Journey of the Sorcerer', 'Persistent ID': 'F56D65BD72A6A9D2', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Eagles/The%20Studio%20Albums%201972-1979/4-04%20Journey%20of%20the%20Sorcerer.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1502': {'Track ID': 1502, 'Name': 'Hammer to Fall (Single Version)', 'Artist': 'Queen', 'Album Artist': 'Queen', 'Composer': 'Brian May', 'Album': 'Queen: Greatest Hits I & II', 'Genre': 'Rock', 'Kind': 'Purchased AAC audio file', 'Size': 8378090, 'Total Time': 220466, 'Disc Number': 2, 'Disc Count': 2, 'Track Number': 14, 'Track Count': 17, 'Year': 1992, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 47), 'Date Added': datetime.datetime(2009, 1, 12, 10, 6, 55), 'Bit Rate': 256, 'Sample Rate': 44100, 'Comments': '(Single Version)', 'Play Count': 6, 'Play Date': 3539150050, 'Play Date UTC': datetime.datetime(2016, 2, 24, 14, 14, 10), 'Normalization': 1999, 'Artwork Count': 1, 'Sort Album': 'Queen: Greatest Hits I & II', 'Sort Artist': 'Queen', 'Sort Name': 'Hammer to Fall (Single Version)', 'Persistent ID': 'FD7EBB2AA180B0BD', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Queen/Queen_%20Greatest%20Hits%20I%20&%20II/2-14%20Hammer%20to%20Fall%20(Single%20Version).m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1504': {'Track ID': 1504, 'Name': 'MacArthur Park', 'Artist': 'Maynard Ferguson', 'Album Artist': 'Maynard Ferguson', 'Composer': 'J. Webb', 'Album': 'This Is Jazz #16', 'Genre': 'Jazz', 'Kind': 'Purchased AAC audio file', 'Size': 20737348, 'Total Time': 597466, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 3, 'Track Count': 8, 'Year': 1996, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 33, 5), 'Date Added': datetime.datetime(2009, 1, 12, 10, 6, 55), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 2, 'Play Date': 3534519038, 'Play Date UTC': datetime.datetime(2016, 1, 1, 23, 50, 38), 'Normalization': 2087, 'Artwork Count': 1, 'Sort Album': 'This Is Jazz #16', 'Sort Artist': 'Maynard Ferguson', 'Sort Name': 'MacArthur Park', 'Persistent ID': '3A68BE18CD0BA222', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Maynard%20Ferguson/This%20Is%20Jazz%20%2316/03%20MacArthur%20Park.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1506': {'Track ID': 1506, 'Name': 'Creep Live At (Le)Poisson Rouge', 'Artist': 'Carrie Manolakos', 'Album Artist': 'Carrie Manolakos', 'Album': 'Creep Live At (Le)Poisson Rouge - Single', 'Genre': 'Pop', 'Kind': 'Purchased AAC audio file', 'Size': 10150198, 'Total Time': 288993, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 1, 'Track Count': 1, 'Year': 2012, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 37), 'Date Added': datetime.datetime(2013, 7, 6, 13, 39, 52), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 14, 'Play Date': 3595065362, 'Play Date UTC': datetime.datetime(2017, 12, 2, 18, 16, 2), 'Skip Count': 3, 'Skip Date': datetime.datetime(2015, 10, 16, 9, 27, 56), 'Normalization': 3919, 'Artwork Count': 1, 'Sort Album': 'Creep Live At (Le)Poisson Rouge - Single', 'Sort Artist': 'Carrie Manolakos', 'Sort Name': 'Creep Live At (Le)Poisson Rouge', 'Persistent ID': '9C8A4A1A1BD27F4A', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Carrie%20Manolakos/Creep%20Live%20At%20(Le)Poisson%20Rouge%20-%20Single/01%20Creep%20Live%20At%20(Le)Poisson%20Rouge.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1510': {'Track ID': 1510, 'Name': 'Big Country', 'Artist': 'Béla Fleck & The Flecktones', 'Album Artist': 'Béla Fleck & The Flecktones', 'Album': 'Béla Fleck & The Flecktones: Greatest Hits of the 20th Century', 'Genre': 'Jazz', 'Kind': 'Purchased AAC audio file', 'Size': 11425238, 'Total Time': 333080, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 10, 'Track Count': 11, 'Year': 1999, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 38), 'Date Added': datetime.datetime(2009, 2, 9, 0, 49, 41), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 7, 'Play Date': 3572419259, 'Play Date UTC': datetime.datetime(2017, 3, 15, 14, 40, 59), 'Skip Date': datetime.datetime(2015, 9, 21, 1, 29, 9), 'Normalization': 1144, 'Compilation': True, 'Artwork Count': 1, 'Sort Album': 'Béla Fleck & The Flecktones: Greatest Hits of the 20th Century', 'Sort Artist': 'Béla Fleck & The Flecktones', 'Sort Name': 'Big Country', 'Persistent ID': '381D03D55DE25C10', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Compilations/Be%CC%81la%20Fleck%20&%20The%20Flecktones_%20Greatest%20Hits%20of%20the%2020th%20Century/10%20Big%20Country.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1526': {'Track ID': 1526, 'Name': 'Take Down the Union Jack', 'Artist': 'Billy Bragg', 'Album Artist': 'Billy Bragg', 'Album': 'England, Half English', 'Genre': 'Singer/Songwriter', 'Kind': 'Purchased AAC audio file', 'Size': 7002723, 'Total Time': 196293, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 8, 'Track Count': 12, 'Year': 2002, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 39), 'Date Added': datetime.datetime(2009, 2, 9, 0, 49, 41), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 3, 'Play Date': 3533618787, 'Play Date UTC': datetime.datetime(2015, 12, 22, 13, 46, 27), 'Normalization': 894, 'Artwork Count': 1, 'Sort Album': 'England, Half English', 'Sort Artist': 'Billy Bragg', 'Sort Name': 'Take Down the Union Jack', 'Persistent ID': '7E380FF256ECC061', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Billy%20Bragg/England,%20Half%20English/08%20Take%20Down%20the%20Union%20Jack.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1530': {'Track ID': 1530, 'Name': "Quelqu'un m'a dit", 'Artist': 'Carla Bruni', 'Album Artist': 'Carla Bruni', 'Composer': 'Carla Bruni & Leos Carax', 'Album': "Quelqu'un m'a dit", 'Genre': 'French Pop', 'Kind': 'Purchased AAC audio file', 'Size': 6045588, 'Total Time': 166226, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 1, 'Track Count': 12, 'Year': 2002, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 46), 'Date Added': datetime.datetime(2013, 11, 5, 9, 28, 21), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 10, 'Play Date': 3544649803, 'Play Date UTC': datetime.datetime(2016, 4, 28, 4, 56, 43), 'Skip Count': 1, 'Normalization': 1652, 'Artwork Count': 1, 'Sort Album': "Quelqu'un m'a dit", 'Sort Artist': 'Carla Bruni', 'Sort Name': "Quelqu'un m'a dit", 'Persistent ID': 'C203874280800632', 'Track Type': 'File', 'Purchased': True, 'Location': "file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Carla%20Bruni/Quelqu'un%20m'a%20dit/01%20Quelqu'un%20m'a%20dit.m4a", 'File Folder Count': 5, 'Library Folder Count': 1}, '1532': {'Track ID': 1532, 'Name': 'Pure Imagination', 'Artist': 'Fiona Apple', 'Album Artist': 'Fiona Apple', 'Album': 'Pure Imagination - Single', 'Genre': 'Soundtrack', 'Kind': 'Purchased AAC audio file', 'Size': 7297858, 'Total Time': 200662, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 1, 'Track Count': 1, 'Year': 2013, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 38), 'Date Added': datetime.datetime(2013, 10, 30, 18, 11, 13), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 30, 'Play Date': 3594874980, 'Play Date UTC': datetime.datetime(2017, 11, 30, 13, 23), 'Skip Count': 1, 'Skip Date': datetime.datetime(2015, 9, 21, 0, 28, 36), 'Rating': 100, 'Album Rating': 100, 'Album Rating Computed': True, 'Normalization': 2078, 'Artwork Count': 1, 'Sort Album': 'Pure Imagination - Single', 'Sort Artist': 'Fiona Apple', 'Sort Name': 'Pure Imagination', 'Persistent ID': 'DAE41C4CAE2D2507', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Fiona%20Apple/Pure%20Imagination%20-%20Single/01%20Pure%20Imagination.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1536': {'Track ID': 1536, 'Name': 'Girls Just Want to Have Fun (Demo)', 'Artist': 'Greg Laswell', 'Album Artist': 'Greg Laswell', 'Album': 'Girls Just Want to Have Fun (Demo) - Single', 'Genre': 'Pop', 'Kind': 'Purchased AAC audio file', 'Size': 5444687, 'Total Time': 156923, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 1, 'Track Count': 1, 'Year': 2007, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 33), 'Date Added': datetime.datetime(2009, 3, 22, 17, 49, 56), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 2, 'Play Date': 3494989336, 'Play Date UTC': datetime.datetime(2014, 10, 1, 10, 22, 16), 'Skip Count': 2, 'Skip Date': datetime.datetime(2015, 9, 21, 0, 28, 14), 'Normalization': 520, 'Artwork Count': 1, 'Sort Album': 'Girls Just Want to Have Fun (Demo) - Single', 'Sort Artist': 'Greg Laswell', 'Sort Name': 'Girls Just Want to Have Fun (Demo)', 'Persistent ID': '9FC859B85CE07F50', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Greg%20Laswell/Girls%20Just%20Want%20to%20Have%20Fun%20(Demo)%20-%20Single/01%20Girls%20Just%20Want%20to%20Have%20Fun%20(Demo).m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1540': {'Track ID': 1540, 'Name': 'Jerusalem', 'Artist': 'Dan Bern', 'Album Artist': 'Dan Bern', 'Composer': 'D. Bern', 'Album': 'Dan Bern', 'Genre': 'Rock', 'Kind': 'Purchased AAC audio file', 'Size': 7969981, 'Total Time': 224426, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 1, 'Track Count': 11, 'Year': 1997, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 31), 'Date Added': datetime.datetime(2009, 1, 12, 10, 6, 55), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 19, 'Play Date': 3531207895, 'Play Date UTC': datetime.datetime(2015, 11, 24, 16, 4, 55), 'Skip Count': 1, 'Skip Date': datetime.datetime(2015, 9, 21, 0, 28, 10), 'Normalization': 1666, 'Artwork Count': 1, 'Sort Album': 'Dan Bern', 'Sort Artist': 'Dan Bern', 'Sort Name': 'Jerusalem', 'Persistent ID': 'F95E7D8FAAB70FFF', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Dan%20Bern/Dan%20Bern/01%20Jerusalem.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1560': {'Track ID': 1560, 'Name': 'See You Space Cowboy...', 'Artist': 'Mai Yamane', 'Composer': 'Yoko Kanno', 'Album': 'Cowboy Bebop OST 3 - BLUE', 'Genre': 'Soundtrack', 'Kind': 'Matched AAC audio file', 'Size': 13047749, 'Total Time': 355501, 'Track Number': 17, 'Year': 1999, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 55), 'Date Added': datetime.datetime(2013, 1, 31, 20, 13, 22), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 1, 'Play Date': 3533876718, 'Play Date UTC': datetime.datetime(2015, 12, 25, 13, 25, 18), 'Normalization': 2052, 'Compilation': True, 'Artwork Count': 1, 'Persistent ID': '4D2E0547AFFBEC46', 'Track Type': 'File', 'Matched': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Compilations/Cowboy%20Bebop%20OST%203%20-%20BLUE/17%20See%20You%20Space%20Cowboy....m4a', 'File Folder Count': 5, 'Library Folder Count': 1}, '1588': {'Track ID': 1588, 'Name': 'Glory Box', 'Artist': 'Portishead', 'Album': 'Dummy', 'Kind': 'Matched AAC audio file', 'Size': 10552169, 'Total Time': 304693, 'Track Number': 11, 'Year': 1994, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 37), 'Date Added': datetime.datetime(2013, 1, 31, 20, 14, 32), 'Bit Rate': 256, 'Sample Rate': 44100, 'Comments': 'Adjusted by iVolume 10/09/2006 23:16:10\n', 'Play Count': 5, 'Play Date': 3562669609, 'Play Date UTC': datetime.datetime(2016, 11, 22, 19, 26, 49), 'Skip Date': datetime.datetime(2015, 9, 21, 1, 28, 54), 'Normalization': 1423, 'Artwork Count': 1, 'Persistent ID': '17475BB17E0B7B90', 'Track Type': 'File', 'Matched': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Portishead/Dummy/11%20Glory%20Box.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}}, 'Playlists': [{'Name': 'cooool', 'Description': '', 'Playlist ID': 31771, 'Playlist Persistent ID': '1DFA8BDCC9C6E8C4', 'All Items': True, 'Playlist Items': [{'Track ID': 1540}, {'Track ID': 1494}, {'Track ID': 1496}, {'Track ID': 1530}, {'Track ID': 1536}, {'Track ID': 1532}, {'Track ID': 1588}, {'Track ID': 1506}, {'Track ID': 1510}, {'Track ID': 1504}, {'Track ID': 1526}, {'Track ID': 1560}, {'Track ID': 1502}, {'Track ID': 1498}]}]}

A good sign!

We can read it!

Sort of, at least. Take a moment and compare what you see rendered by the plistlib with the original text of the .xml file (below). You can see that there have been some significant changes: The header is gone; in the processed code, single quotes (above) have replaced most tags (below). And so on.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Major Version</key><integer>1</integer>
    <key>Minor Version</key><integer>1</integer>
    <key>Date</key><date>2018-02-08T16:16:42Z</date>
    <key>Application Version</key><string>12.7.3.46</string>
    <key>Features</key><integer>5</integer>
    <key>Show Content Ratings</key><true/>
In [7]:
import plistlib
import pandas
In [8]:
fileName = 'drm_itunes.xml'
myFileObject = open(fileName, 'rb')
songLibraryData = plistlib.load(myFileObject)
In [9]:
# Prep the arrays to hold the lists of data
# trackSerial will be where we store trackID:  The four-digit
# code assigned each track inside the .xml file
# The only non-obvious one is trackTime:
# In the .xml, that is an integer describing the
# the length of a song in milliseconds.
# So for minutes, we need to divide by 1000
# and then by 60...

trackSerial=[]
trackName=[]
trackArtist=[]
trackAlbum=[]
trackTime = []
trackYear = []
In [10]:
# If you look closely at the .xml file, you'll see
# that most of the data is organized into sets of curly
# braces.  If we look at the top-most name for those sets,
# we see that it is 'Tracks'.  The library is organized
# into units called 'Tracks'.
{
    'Major Version': 1, 
    'Minor Version': 1,
    'Features': 5, 
    'Show Content Ratings': True, 
    'Tracks': {
        '1494': {
            'Track ID': 1494, 
            'Name': 'Take on Me', 
            'Artist': 'NO BS! Brass',
            'Library Folder Count': 1
             }, 
         '1496': {
             'Track ID': 1496, 
             'Name': 'Splatter Splatter', 
             'Artist': 'Moxy Fruvous'...
             }

Again, this is basically just a Dictionary, and everything is ordered in KVPs: Key-Value Pairs. The Key is the term you look up, and the Value is its definition. They are separated by a colon.

THE TRICK HERE is that the VALUE of a KEY is frequently more than just a number or a word. It often is, for example, a second KVP. This sounds complicated. It is not: Just work backwards from our earlier metaphor: If we use a dictionary to keep track of the meaning of each word, then how might we keep track of the different kinds of dictionaries we keep? Right: Inside a dictionary of dictionaries.

The important thing is that everything resolves to a single K and a single V -- even if that single V is actually a pair of curly braces with lots and lots of messy things deep down inside it. We just need to be sure that if we climb all the way to the top of the data set, we have one K for one V.

In [11]:
# That's what we've got here.  'Tracks' is one of the topmost
# Keys in our .XML file.  Inside the VALUE for that one Key
# is our entire music collection.  So it makes sense that
# it is the Key we want to open:
tracks = songLibraryData['Tracks']
In [12]:
# in order to iterate through data, Python
# prefers this for statement.  You'll see
# it everywhere you go.

# Here's an archetypal version of it:
# for KEY, VALUE in DICTIONARY.items():
# It just means "Get a key and a value
# from each item in this dictionary."

# As I get each value, I "append" that
# information to a list of similar data.
# (Append just means "add to the end
# of the current list").

# So for trackYear, for example:
# trackYear = ['1994']
# and then trackYear.append('1999')
# so trackYear = ['1994', '1999']
# and then trackYear.append('2000')
# so trackYear = ['1994', '1999', '2000']

for trackID,track in tracks.items():
    trackSerial.append(trackID)
    trackName.append(track['Name'])
    trackAlbum.append(track['Album'])
    trackArtist.append(track['Artist'])
    trackYear.append(track['Year'])
    trackTime.append(track['Total Time'])
In [30]:
for ID,T in tracks.items():
    print(T)
    print("___")
{'Track ID': 1494, 'Name': 'Take on Me', 'Artist': 'NO BS! Brass', 'Album Artist': 'NO BS! Brass', 'Album': 'Alive in Richmond 2.0', 'Genre': 'Jazz', 'Kind': 'Purchased AAC audio file', 'Size': 7379988, 'Total Time': 195440, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 16, 'Track Count': 18, 'Year': 2011, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 36), 'Date Added': datetime.datetime(2014, 9, 22, 19, 17, 57), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 8, 'Play Date': 3541067864, 'Play Date UTC': datetime.datetime(2016, 3, 17, 17, 57, 44), 'Skip Count': 1, 'Skip Date': datetime.datetime(2015, 10, 13, 18, 22, 24), 'Loved': True, 'Normalization': 5314, 'Artwork Count': 1, 'Sort Album': 'Alive in Richmond 2.0', 'Sort Artist': 'NO BS! Brass', 'Sort Name': 'Take on Me', 'Persistent ID': 'E3B56D40EF180A73', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/NO%20BS!%20Brass/Alive%20in%20Richmond%202.0/16%20Take%20on%20Me.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1496, 'Name': 'Splatter Splatter', 'Artist': 'Moxy Fruvous', 'Album Artist': 'Moxy Fruvous', 'Composer': 'Unknown', 'Album': 'Thornhill', 'Genre': 'Rock', 'Kind': 'Purchased AAC audio file', 'Size': 6476758, 'Total Time': 179731, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 7, 'Track Count': 12, 'Year': 1999, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 32), 'Date Added': datetime.datetime(2013, 11, 7, 0, 31), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 21, 'Play Date': 3575360579, 'Play Date UTC': datetime.datetime(2017, 4, 18, 15, 42, 59), 'Skip Count': 2, 'Skip Date': datetime.datetime(2015, 9, 21, 0, 28, 30), 'Normalization': 6789, 'Artwork Count': 1, 'Sort Album': 'Thornhill', 'Sort Artist': 'Moxy Fruvous', 'Sort Name': 'Splatter Splatter', 'Persistent ID': '2C75C312BA0835DA', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Moxy%20Fruvous/Thornhill/07%20Splatter%20Splatter.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1498, 'Name': 'Journey of the Sorcerer', 'Artist': 'Eagles', 'Album Artist': 'Eagles', 'Album': 'The Studio Albums 1972-1979', 'Genre': 'Rock', 'Kind': 'Purchased AAC audio file', 'Size': 13972779, 'Total Time': 396921, 'Disc Number': 4, 'Disc Count': 6, 'Track Number': 4, 'Track Count': 9, 'Year': 2013, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 52), 'Date Added': datetime.datetime(2013, 10, 31, 9, 33, 54), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 11, 'Play Date': 3594875816, 'Play Date UTC': datetime.datetime(2017, 11, 30, 13, 36, 56), 'Skip Count': 1, 'Normalization': 1270, 'Artwork Count': 1, 'Sort Album': 'Studio Albums 1972-1979', 'Sort Artist': 'Eagles', 'Sort Name': 'Journey of the Sorcerer', 'Persistent ID': 'F56D65BD72A6A9D2', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Eagles/The%20Studio%20Albums%201972-1979/4-04%20Journey%20of%20the%20Sorcerer.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1502, 'Name': 'Hammer to Fall (Single Version)', 'Artist': 'Queen', 'Album Artist': 'Queen', 'Composer': 'Brian May', 'Album': 'Queen: Greatest Hits I & II', 'Genre': 'Rock', 'Kind': 'Purchased AAC audio file', 'Size': 8378090, 'Total Time': 220466, 'Disc Number': 2, 'Disc Count': 2, 'Track Number': 14, 'Track Count': 17, 'Year': 1992, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 47), 'Date Added': datetime.datetime(2009, 1, 12, 10, 6, 55), 'Bit Rate': 256, 'Sample Rate': 44100, 'Comments': '(Single Version)', 'Play Count': 6, 'Play Date': 3539150050, 'Play Date UTC': datetime.datetime(2016, 2, 24, 14, 14, 10), 'Normalization': 1999, 'Artwork Count': 1, 'Sort Album': 'Queen: Greatest Hits I & II', 'Sort Artist': 'Queen', 'Sort Name': 'Hammer to Fall (Single Version)', 'Persistent ID': 'FD7EBB2AA180B0BD', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Queen/Queen_%20Greatest%20Hits%20I%20&%20II/2-14%20Hammer%20to%20Fall%20(Single%20Version).m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1504, 'Name': 'MacArthur Park', 'Artist': 'Maynard Ferguson', 'Album Artist': 'Maynard Ferguson', 'Composer': 'J. Webb', 'Album': 'This Is Jazz #16', 'Genre': 'Jazz', 'Kind': 'Purchased AAC audio file', 'Size': 20737348, 'Total Time': 597466, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 3, 'Track Count': 8, 'Year': 1996, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 33, 5), 'Date Added': datetime.datetime(2009, 1, 12, 10, 6, 55), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 2, 'Play Date': 3534519038, 'Play Date UTC': datetime.datetime(2016, 1, 1, 23, 50, 38), 'Normalization': 2087, 'Artwork Count': 1, 'Sort Album': 'This Is Jazz #16', 'Sort Artist': 'Maynard Ferguson', 'Sort Name': 'MacArthur Park', 'Persistent ID': '3A68BE18CD0BA222', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Maynard%20Ferguson/This%20Is%20Jazz%20%2316/03%20MacArthur%20Park.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1506, 'Name': 'Creep Live At (Le)Poisson Rouge', 'Artist': 'Carrie Manolakos', 'Album Artist': 'Carrie Manolakos', 'Album': 'Creep Live At (Le)Poisson Rouge - Single', 'Genre': 'Pop', 'Kind': 'Purchased AAC audio file', 'Size': 10150198, 'Total Time': 288993, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 1, 'Track Count': 1, 'Year': 2012, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 37), 'Date Added': datetime.datetime(2013, 7, 6, 13, 39, 52), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 14, 'Play Date': 3595065362, 'Play Date UTC': datetime.datetime(2017, 12, 2, 18, 16, 2), 'Skip Count': 3, 'Skip Date': datetime.datetime(2015, 10, 16, 9, 27, 56), 'Normalization': 3919, 'Artwork Count': 1, 'Sort Album': 'Creep Live At (Le)Poisson Rouge - Single', 'Sort Artist': 'Carrie Manolakos', 'Sort Name': 'Creep Live At (Le)Poisson Rouge', 'Persistent ID': '9C8A4A1A1BD27F4A', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Carrie%20Manolakos/Creep%20Live%20At%20(Le)Poisson%20Rouge%20-%20Single/01%20Creep%20Live%20At%20(Le)Poisson%20Rouge.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1510, 'Name': 'Big Country', 'Artist': 'Béla Fleck & The Flecktones', 'Album Artist': 'Béla Fleck & The Flecktones', 'Album': 'Béla Fleck & The Flecktones: Greatest Hits of the 20th Century', 'Genre': 'Jazz', 'Kind': 'Purchased AAC audio file', 'Size': 11425238, 'Total Time': 333080, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 10, 'Track Count': 11, 'Year': 1999, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 38), 'Date Added': datetime.datetime(2009, 2, 9, 0, 49, 41), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 7, 'Play Date': 3572419259, 'Play Date UTC': datetime.datetime(2017, 3, 15, 14, 40, 59), 'Skip Date': datetime.datetime(2015, 9, 21, 1, 29, 9), 'Normalization': 1144, 'Compilation': True, 'Artwork Count': 1, 'Sort Album': 'Béla Fleck & The Flecktones: Greatest Hits of the 20th Century', 'Sort Artist': 'Béla Fleck & The Flecktones', 'Sort Name': 'Big Country', 'Persistent ID': '381D03D55DE25C10', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Compilations/Be%CC%81la%20Fleck%20&%20The%20Flecktones_%20Greatest%20Hits%20of%20the%2020th%20Century/10%20Big%20Country.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1526, 'Name': 'Take Down the Union Jack', 'Artist': 'Billy Bragg', 'Album Artist': 'Billy Bragg', 'Album': 'England, Half English', 'Genre': 'Singer/Songwriter', 'Kind': 'Purchased AAC audio file', 'Size': 7002723, 'Total Time': 196293, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 8, 'Track Count': 12, 'Year': 2002, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 39), 'Date Added': datetime.datetime(2009, 2, 9, 0, 49, 41), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 3, 'Play Date': 3533618787, 'Play Date UTC': datetime.datetime(2015, 12, 22, 13, 46, 27), 'Normalization': 894, 'Artwork Count': 1, 'Sort Album': 'England, Half English', 'Sort Artist': 'Billy Bragg', 'Sort Name': 'Take Down the Union Jack', 'Persistent ID': '7E380FF256ECC061', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Billy%20Bragg/England,%20Half%20English/08%20Take%20Down%20the%20Union%20Jack.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1530, 'Name': "Quelqu'un m'a dit", 'Artist': 'Carla Bruni', 'Album Artist': 'Carla Bruni', 'Composer': 'Carla Bruni & Leos Carax', 'Album': "Quelqu'un m'a dit", 'Genre': 'French Pop', 'Kind': 'Purchased AAC audio file', 'Size': 6045588, 'Total Time': 166226, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 1, 'Track Count': 12, 'Year': 2002, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 46), 'Date Added': datetime.datetime(2013, 11, 5, 9, 28, 21), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 10, 'Play Date': 3544649803, 'Play Date UTC': datetime.datetime(2016, 4, 28, 4, 56, 43), 'Skip Count': 1, 'Normalization': 1652, 'Artwork Count': 1, 'Sort Album': "Quelqu'un m'a dit", 'Sort Artist': 'Carla Bruni', 'Sort Name': "Quelqu'un m'a dit", 'Persistent ID': 'C203874280800632', 'Track Type': 'File', 'Purchased': True, 'Location': "file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Carla%20Bruni/Quelqu'un%20m'a%20dit/01%20Quelqu'un%20m'a%20dit.m4a", 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1532, 'Name': 'Pure Imagination', 'Artist': 'Fiona Apple', 'Album Artist': 'Fiona Apple', 'Album': 'Pure Imagination - Single', 'Genre': 'Soundtrack', 'Kind': 'Purchased AAC audio file', 'Size': 7297858, 'Total Time': 200662, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 1, 'Track Count': 1, 'Year': 2013, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 38), 'Date Added': datetime.datetime(2013, 10, 30, 18, 11, 13), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 30, 'Play Date': 3594874980, 'Play Date UTC': datetime.datetime(2017, 11, 30, 13, 23), 'Skip Count': 1, 'Skip Date': datetime.datetime(2015, 9, 21, 0, 28, 36), 'Rating': 100, 'Album Rating': 100, 'Album Rating Computed': True, 'Normalization': 2078, 'Artwork Count': 1, 'Sort Album': 'Pure Imagination - Single', 'Sort Artist': 'Fiona Apple', 'Sort Name': 'Pure Imagination', 'Persistent ID': 'DAE41C4CAE2D2507', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Fiona%20Apple/Pure%20Imagination%20-%20Single/01%20Pure%20Imagination.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1536, 'Name': 'Girls Just Want to Have Fun (Demo)', 'Artist': 'Greg Laswell', 'Album Artist': 'Greg Laswell', 'Album': 'Girls Just Want to Have Fun (Demo) - Single', 'Genre': 'Pop', 'Kind': 'Purchased AAC audio file', 'Size': 5444687, 'Total Time': 156923, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 1, 'Track Count': 1, 'Year': 2007, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 33), 'Date Added': datetime.datetime(2009, 3, 22, 17, 49, 56), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 2, 'Play Date': 3494989336, 'Play Date UTC': datetime.datetime(2014, 10, 1, 10, 22, 16), 'Skip Count': 2, 'Skip Date': datetime.datetime(2015, 9, 21, 0, 28, 14), 'Normalization': 520, 'Artwork Count': 1, 'Sort Album': 'Girls Just Want to Have Fun (Demo) - Single', 'Sort Artist': 'Greg Laswell', 'Sort Name': 'Girls Just Want to Have Fun (Demo)', 'Persistent ID': '9FC859B85CE07F50', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Greg%20Laswell/Girls%20Just%20Want%20to%20Have%20Fun%20(Demo)%20-%20Single/01%20Girls%20Just%20Want%20to%20Have%20Fun%20(Demo).m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1540, 'Name': 'Jerusalem', 'Artist': 'Dan Bern', 'Album Artist': 'Dan Bern', 'Composer': 'D. Bern', 'Album': 'Dan Bern', 'Genre': 'Rock', 'Kind': 'Purchased AAC audio file', 'Size': 7969981, 'Total Time': 224426, 'Disc Number': 1, 'Disc Count': 1, 'Track Number': 1, 'Track Count': 11, 'Year': 1997, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 31), 'Date Added': datetime.datetime(2009, 1, 12, 10, 6, 55), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 19, 'Play Date': 3531207895, 'Play Date UTC': datetime.datetime(2015, 11, 24, 16, 4, 55), 'Skip Count': 1, 'Skip Date': datetime.datetime(2015, 9, 21, 0, 28, 10), 'Normalization': 1666, 'Artwork Count': 1, 'Sort Album': 'Dan Bern', 'Sort Artist': 'Dan Bern', 'Sort Name': 'Jerusalem', 'Persistent ID': 'F95E7D8FAAB70FFF', 'Track Type': 'File', 'Purchased': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Dan%20Bern/Dan%20Bern/01%20Jerusalem.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1560, 'Name': 'See You Space Cowboy...', 'Artist': 'Mai Yamane', 'Composer': 'Yoko Kanno', 'Album': 'Cowboy Bebop OST 3 - BLUE', 'Genre': 'Soundtrack', 'Kind': 'Matched AAC audio file', 'Size': 13047749, 'Total Time': 355501, 'Track Number': 17, 'Year': 1999, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 55), 'Date Added': datetime.datetime(2013, 1, 31, 20, 13, 22), 'Bit Rate': 256, 'Sample Rate': 44100, 'Play Count': 1, 'Play Date': 3533876718, 'Play Date UTC': datetime.datetime(2015, 12, 25, 13, 25, 18), 'Normalization': 2052, 'Compilation': True, 'Artwork Count': 1, 'Persistent ID': '4D2E0547AFFBEC46', 'Track Type': 'File', 'Matched': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Compilations/Cowboy%20Bebop%20OST%203%20-%20BLUE/17%20See%20You%20Space%20Cowboy....m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
{'Track ID': 1588, 'Name': 'Glory Box', 'Artist': 'Portishead', 'Album': 'Dummy', 'Kind': 'Matched AAC audio file', 'Size': 10552169, 'Total Time': 304693, 'Track Number': 11, 'Year': 1994, 'Date Modified': datetime.datetime(2015, 9, 21, 1, 32, 37), 'Date Added': datetime.datetime(2013, 1, 31, 20, 14, 32), 'Bit Rate': 256, 'Sample Rate': 44100, 'Comments': 'Adjusted by iVolume 10/09/2006 23:16:10\n', 'Play Count': 5, 'Play Date': 3562669609, 'Play Date UTC': datetime.datetime(2016, 11, 22, 19, 26, 49), 'Skip Date': datetime.datetime(2015, 9, 21, 1, 28, 54), 'Normalization': 1423, 'Artwork Count': 1, 'Persistent ID': '17475BB17E0B7B90', 'Track Type': 'File', 'Matched': True, 'Location': 'file:///Users/garrisonlemasters/Music/iTunes/iTunes%20Media/Music/Portishead/Dummy/11%20Glory%20Box.m4a', 'File Folder Count': 5, 'Library Folder Count': 1}
___
In [13]:
# Look at some random data
print(trackName[2])
print(trackAlbum[6])
print(trackArtist[1])

# Just a word of warning:  We're not slowing down
# to bother with Error Checking, although we
# probably should.  Because of that, if any of 
# our data is off-kilter or is missing a line or
# two of values, everything will go to hell quickly.
# With "real" data, you can't afford to skip
# error-handling.  Here we can, because I've
# pre-screened this dataset and I know it is
# 100% complete.
Journey of the Sorcerer
Béla Fleck & The Flecktones: Greatest Hits of the 20th Century
Moxy Fruvous
In [14]:
# You know why I love the PANDAS library so much?  
# Because it always seems to work.  Plus I love
# writing code that talks to pandas.

TianTian = pandas.DataFrame({
    'TrackID':trackSerial,
    'Name':trackName,
    'Album':trackAlbum,
    'Artist':trackArtist,
    'Year':trackYear,
    'Duration':trackTime
    })

The Payoff

The payoff here is pretty great, I think. Yes, it took a while to get here, but look at what we can do in a single line. Below, I'm telling TianTian to sort his list by Year, and to add a bar graph to the Duration column, showing relative lengths of each song.'

In [15]:
TianTian.sort_values(by='Year').style.bar(subset='Duration', color='#ffcc33')
Out[15]:
Album Artist Duration Name TrackID Year
3 Queen: Greatest Hits I & II Queen 220466 Hammer to Fall (Single Version) 1502 1992
13 Dummy Portishead 304693 Glory Box 1588 1994
4 This Is Jazz #16 Maynard Ferguson 597466 MacArthur Park 1504 1996
11 Dan Bern Dan Bern 224426 Jerusalem 1540 1997
1 Thornhill Moxy Fruvous 179731 Splatter Splatter 1496 1999
6 Béla Fleck & The Flecktones: Greatest Hits of the 20th Century Béla Fleck & The Flecktones 333080 Big Country 1510 1999
12 Cowboy Bebop OST 3 - BLUE Mai Yamane 355501 See You Space Cowboy... 1560 1999
7 England, Half English Billy Bragg 196293 Take Down the Union Jack 1526 2002
8 Quelqu'un m'a dit Carla Bruni 166226 Quelqu'un m'a dit 1530 2002
10 Girls Just Want to Have Fun (Demo) - Single Greg Laswell 156923 Girls Just Want to Have Fun (Demo) 1536 2007
0 Alive in Richmond 2.0 NO BS! Brass 195440 Take on Me 1494 2011
5 Creep Live At (Le)Poisson Rouge - Single Carrie Manolakos 288993 Creep Live At (Le)Poisson Rouge 1506 2012
2 The Studio Albums 1972-1979 Eagles 396921 Journey of the Sorcerer 1498 2013
9 Pure Imagination - Single Fiona Apple 200662 Pure Imagination 1532 2013
In [16]:
# Finally, saving from PANDAS is soooo easy:  Ready?
TianTian.to_csv("myTunesData.csv")