Now that I had a XML file representing everything in my playlist, I needed to be able to actually use it within my Python script… so I could then, in turn, generate my daily playlist. I’m using PyPy, which is a rather unique variation of Python that can drastically speed up the performance of Python… so I figured why not? Anyway, this does pose some limitations on how I can proceed, as a number of Python XML parsing libraries are built in C with Python hooks, something PyPy doesn’t handle gracefully (yet). So I decided to stick with what I had in the Python Standard Library, which included the ever so convenient ElementTree that quickly and easily allows for me to process an XML file.
Of course, I needed to make a Python object to load the tracks into, so first off, I built my Track object:
class Track:
__slots__ = ('disc','discTotal','track','trackTotal','title','album','year','artist','albumArtist','genre','flags','filename','fileDate','length','size','channels','sampleRate','bitRate','fileType','fileTag','added','played')
def __init__(self):
self.disc = 0
self.discTotal = 0
self.track = 0
self.trackTotal = 0
self.title = ""
self.album = ""
self.year = ""
self.artist = ""
self.albumArtist = ""
self.genre = ""
self.flags = []
self.filename = ""
self.fileDate = ""
self.length = 0
self.size = 0
self.channels = ""
self.sampleRate = ""
self.bitRate = ""
self.fileType = ""
self.fileTag = ""
self.added = datetime.today()
self.played = []
def toTuple(self):
dat = (
self.disc, self.discTotal, self.track, self.trackTotal,
self.title, self.album, self.year, self.artist,
self.albumArtist, self.genre, self.flags, self.filename,
self.fileDate, self.length, self.size, self.channels,
self.sampleRate, self.bitRate, self.fileType,
self.fileTag
)
return dat
def __eq__(self, other):
if 'toTuple' not in dir(other):
return False
return self.toTuple().__eq__(other.toTuple())
def __ne__(self, other):
if 'toTuple' not in dir(other):
return False
return self.toTuple().__ne__(other.toTuple())
def __lt__(self, other):
if 'toTuple' not in dir(other):
return False
return self.toTuple().__lt__(other.toTuple())
def __le__(self, other):
if 'toTuple' not in dir(other):
return False
return self.toTuple().__le__(other.toTuple())
def __gt__(self, other):
if 'toTuple' not in dir(other):
return False
return self.toTuple().__gt__(other.toTuple())
def __ge__(self, other):
if 'toTuple' not in dir(other):
return False
return self.toTuple().__ge__(other.toTuple())
def checkSum(self):
return hashlib.sha512(str(self.toTuple())).hexdigest()
def copy(self, other):
log.info("Changing "%s":", self.filename)
logLine = "t%s (%s -> %s)"
logLineQ = "t%s ("%s" -> "%s")"
if self.disc != other.disc:
log.info(logLine, "Disc", str(self.disc), str(other.disc))
self.disc = other.disc
if self.discTotal != other.discTotal:
log.info(logLine, "Disc Total", str(self.discTotal), str(other.discTotal))
self.discTotal = other.discTotal
if self.track != other.track:
log.info(logLine, "Track", str(self.track), str(other.track))
self.track = other.track
if self.trackTotal != other.trackTotal:
log.info(logLine, "Track Total", str(self.trackTotal), str(other.trackTotal))
self.trackTotal = other.trackTotal
if self.title != other.title:
log.info(logLineQ, "Title", str(self.title), str(other.title))
self.title = other.title
if self.album != other.album:
log.info(logLineQ, "Album", str(self.album), str(other.album))
self.album = other.album
if self.year != other.year:
log.info(logLine, "Year", str(self.year), str(other.year))
self.year = other.year
if self.artist != other.artist:
log.info(logLineQ, "Artist", str(self.artist), str(other.artist))
self.artist = other.artist
if self.albumArtist != other.albumArtist:
log.info(logLineQ, "Album Artist", str(self.albumArtist), str(other.albumArtist))
self.albumArtist = other.albumArtist
if self.genre != other.genre:
log.info(logLineQ, "Genre", str(self.genre), str(other.genre))
self.genre = other.genre
if self.flags != other.flags:
log.info(logLineQ, "Flags", str(self.flags), str(other.flags))
self.flags = other.flags
if self.filename != other.filename:
log.info(logLineQ, "Filename", str(self.filename), str(other.filename))
self.filename = other.filename
if self.fileDate != other.fileDate:
log.info(logLine, "File Date", str(self.fileDate), str(other.fileDate))
self.fileDate = other.fileDate
if self.length != other.length:
log.info(logLine, "Length", str(timedelta(0, self.length)), str(timedelta(0, other.length)))
self.length = other.length
if self.size != other.size:
log.info(logLine, "Size", str(self.size), str(other.size))
self.size = other.size
if self.channels != other.channels:
log.info(logLineQ, "Channels", str(self.channels), str(other.channels))
self.channels = other.channels
if self.sampleRate != other.sampleRate:
log.info(logLineQ, "Sample Rate", str(self.sampleRate), str(other.sampleRate))
self.sampleRate = other.sampleRate
if self.bitRate != other.bitRate:
log.info(logLineQ, "Bit Rate", str(self.bitRate), str(other.bitRate))
self.bitRate = other.bitRate
if self.fileType != other.fileType:
log.info(logLineQ, "File Type", str(self.fileType), str(other.fileType))
self.fileType = other.fileType
if self.fileTag != other.fileTag:
log.info(logLineQ, "File Tag", str(self.fileTag), str(other.fileTag))
self.fileTag = other.fileTag
This basically is an object that just holds onto the various XML sub-elements for the XML track into a single object for me to work with. It has a couple of other pieces, namely it some some overriding for comparison operators, has a way to copy a track from another track, and has a way to produce a tuple representation of the object that I use with the difflib for figuring out when two tracks are most likely the same (explained later).
Then, there is the actual import function:
def importData():
"""
Imports a new set of data from the XML file.
"""
listIter = ET.iterparse(XML_FILENAME)
tracks = {}
track = Track()
trackCat = None
for listItem in listIter:
trackElem = listItem[1]
if trackElem.tag == "track":
if trackCat not in tracks:
tracks[trackCat] = []
tracks[trackCat].append(track)
track = Track()
elif trackElem.tag == "discnumber":
track.disc = int(trackElem.text) if trackElem.text != None else None
elif trackElem.tag == "totaldiscs":
track.discTotal = int(trackElem.text) if trackElem.text != None else None
elif trackElem.tag == "tracknumber":
track.track = int(trackElem.text) if trackElem.text != None else None
elif trackElem.tag == "totaltracks":
track.trackTotal = int(trackElem.text) if trackElem.text != None else None
elif trackElem.tag == "title":
if trackElem.text == None:
track.title = u''
else:
track.title = trackElem.text
elif trackElem.tag == "album":
if trackElem.text == None:
track.album = u''
else:
track.album = trackElem.text
elif trackElem.tag == "year":
if trackElem.text == None:
track.year = u''
else:
track.year = trackElem.text
elif trackElem.tag == "artist":
if trackElem.text == None:
track.artist = u''
else:
track.artist = trackElem.text
elif trackElem.tag == "albumartist":
if trackElem.text == None:
track.albumArtist = u''
else:
track.albumArtist = trackElem.text
elif trackElem.tag == "genre":
if trackElem.text == None:
track.genre = u''
else:
track.genre = trackElem.text
elif trackElem.tag == "category":
if trackElem.text == None:
tracvCat = u''
else:
trackCat = trackElem.text
elif trackElem.tag == "flags":
if trackElem.text == None:
track.flags = []
else:
track.flags = trackElem.text.split(",")
elif trackElem.tag == "filename":
if trackElem.text == None:
track.filename = u''
else:
track.filename = trackElem.text
elif trackElem.tag == "filedate":
track.fileDate = datetime.strptime(trackElem.text, '%m/%d/%Y %I:%M:%S %p') if trackElem.text != None else None
elif trackElem.tag == "length":
track.length = int(trackElem.text) if trackElem.text != None else None
elif trackElem.tag == "size":
track.size = int(trackElem.text) if trackElem.text != None else None
elif trackElem.tag == "channels":
if trackElem.text == None:
track.channels = u''
else:
track.channels = trackElem.text
elif trackElem.tag == "samplerate":
track.sampleRate = int(trackElem.text) if trackElem.text != None else None
elif trackElem.tag == "bitrate":
track.bitRate = int(trackElem.text) if trackElem.text != None else None
elif trackElem.tag == "type":
if trackElem.text == None:
track.fileType = u''
else:
track.fileType = trackElem.text
elif trackElem.tag == "tag":
if trackElem.text == None:
track.fileTag = u''
else:
track.fileTag = trackElem.text
for trackList in tracks.values():
random.shuffle(trackList)
for track in trackList:
track.added = datetime.today()
return tracks
This does two things, it rapidly loads every track from the XML file, and then… because I have had to rebuild my datafile a number of times and don’t want to keep playing the same set of tracks (through the LRA process explained later), I shuffle the tracks and assign the added date in random order.
Once this is completed, I am able to use the tracks in Python using the tracks object I built. I partition my music list along “category” lines, which I use the “categorygroup” or other equivalent tags within my music to identify what goes to which category. Everything about my playlist works from this set of categories — no song exists in two categories, so it really does a complete partitioning of my music, which means I can process each category completely independent of each other… something very useful later in the code.