PyTunes Playlist

 

Intro

Every now and then I’ve found myself trying to share some iTunes playlists with friends, or even with myself but found no easy solution for it. iTunes exports XML files with the information about the songs, so I figured there would be a way to use the path of the songs to copy them into a folder that could be shared either to friends, or to my android phone.

CodeDev

Exploring XML

First thing was to explore the XML that iTunes exports. After some looking around, we can see that the bottom part of the file contains the information of the playlist and the ordering of the songs by Track ID:

On the top of the XML, we see that the information of the songs is laid down in dict entries:

Parsing songs’ info

With this information we can start doing some coding. First, we parse our tree and lay down the general actions we need to take the tree and extract the information about the songs, particularly, we want to get their ID, their paths, and filenames.

def getSongsDictionary(tree):
    # Parses the Track IDs of the songs and returns a dictionary containing
    #    {songID: [songPath, filename]}
    sID = getSongsID(tree)
    sPA = getSongsPaths(tree)
    filenames = [i.split("/")[-1] for i in sPA]
    songsList = list(zip(sID, sPA, filenames))
    songsDict = {song[0]: [song[1], song[2]] for song in songsList}
    return(songsDict)

def getSongsID(tree):
    # Parse the songs Track ID information from the XML
    #   (first entry of the nested dict is always the key==Track ID, which
    #   is composed by digits only)
    songs = tree.findall('./dict/dict/')
    songsIds = []
    for song in songs:
        textId = song.text
        if (textId.isdigit()):
            songsIds.append(textId)
    return(songsIds)

def getSongsPaths(tree):
  # Parse the file path information from the XML
  #   (string tag starting with "file://")
  songs = tree.findall('./dict/dict/dict/string')
  songsPaths = []
  for song in songs:
      if song.text[0:7] == "file://":
          myfile = unquote(song.text[7:])
          songsPaths.append(myfile)
  return(songsPaths)`

After doing so, we want to obtain the ordering of the songs in the playlist.

def getSongsOrder(tree):
    # Look at the tail end of the XML and parse the Track IDs as they
    #   are stored in the playlist in iTunes
    songs = tree.findall('./dict/array/dict/array/dict/integer')
    ordersId = []
    for song in songs:
        ordersId.append(song.text)
    return(ordersId)

def getOrderedSongs(playlistXML):
    # Parses and sorts the songs according to the playlist XML
    tree = ElementTree().parse(playlistXML)
    songsDict = getSongsDictionary(tree)
    sOR = getSongsOrder(tree)
    orderedSongs = [songsDict[i] for i in sOR]
    return(orderedSongs)

Sorting playlist

With these pieces in place, we sort the songs dictionaries according to the sorting provided by the playlist info. Which takes us to the final wrapper:

def getIOPaths(playlistXML, destiny, padSize=3):
    # Full wrapper to get the sorted songs with paths for copy
    playlist = getOrderedSongs(playlistXML)
    listIO = []
    for (i, song) in enumerate(playlist):
        outPath = destiny + str(i + 1).rjust(padSize, "0") + "_" + song[1]
        listIO.append((song[0], outPath))
        print("I: " + song[0] + "\n" + "O: " + outPath + "\n")
    return(listIO)

Copying files

Once we have our final wrapper, we simply put it all together by adding a copyfile call from shutil:

import os
import playlistAux as aux
from shutil import copyfile
###############################################################################
# Define IO
src = "/Users/sanchez.hmsc/Desktop/5Star.xml"
dst = "/Users/sanchez.hmsc/Desktop/5Star/"
###############################################################################
# Get the playlist in shape
files = aux.getIOPaths(src, dst)
###############################################################################
# Do the copy in disk
if not os.path.exists(dst):
    os.mkdir(dst)

for (i, song) in enumerate(files):
    try:
        copyfile(song[0], song[1])
    except (IOError):
        print('Error in file: ' + song[0])

print("Songs (" + str(len(files)) + ") were successfully copied.")

Code repo