Fixing Up Sort Related Tags for iTunes

While I use iTunes, it is not the center of my digital music universe. My music library at home is shared between a number of devices1. As such I strive to keep my files as compatible as possible between numerous pieces of hardware and software. I try to keep all my metadata in the files ID3 tags, and I’ve had the best luck with ID3v2.3 working across the board.

The other day I started playing with MusicBrainz Picard to flesh out my metadata. It’s pretty slick, and it even gives you the option to write ID3v2.3 tags (rather than the default of ID3v2.4). I was pretty psyched, until I noticed iTunes ignoring the sort tags. So while my “Frank Black and the Catholics” tracks were getting a “Sort Artist” tag of “Black, Frank and Catholics, the”, iTunes didn’t see them.

A bit of searching showed that the ID3v2.4 spec introduced the TSOA (Album sort order), TSOP (Performer sort order), and TSOT (Title sort order) fields (iTunes also added the “unofficial” TSO2 for Album Artist sort order). A bit more searching showed that many apps and devices will support these tags in ID3v2.3. Picard when writing these tags out to ID3v2.3 stored stem in XSOA, XSOP, XSOT, and a TXXX frame with the description ALBUMARTISTSORT What Picard is doing is technically correct, but it seems most everything (including iTunes) will happily read these ID3v2.4 frames out of a ID3v2.3 tag correctly2.

The Fix

So I turned to my favorite Python ID3 manipulation library, eyeD3, and came up with the following:


def convertFrames(tag,oldID,newID):
     count = 0
     for frm in tag.frames[oldID]:
          frm.header.id = newID
          count = count + 1

     for frm in tag.frames["TXXX"]:
          if (frm.description == oldID):
               frm.header.id = newID
               frm.description = frm.text
               frm.text = ""
               count = count + 1

     return count

To perform the conversion I’m interested in I just need to call this the following on each file I’m interested in:


convertFrames(tag,'XSOP','TSOP')
convertFrames(tag,'XSOT','TSOT')
convertFrames(tag,'XSOA','TSOA')
convertFrames(tag,'ALBUMARTISTSORT','TSO2')
tag.update(eyeD3.ID3_V2_3) # ensure correct tag version

After running this on my library I just had to get iTunes to re-read the meta data from the files. You can do this on any single file by doing a Get Info on it, but doing it for a batch of files is just as easy, if not nearly as obvious. Select all the files, right-click, and choose Uncheck Selection, then right-click again and choose Check Selection3.

Full Listing


#!/usr/bin/python

"""
FixSortTags -- Convert sort tag frames found it ID3 tag iTunes compatible
ID3v2.3 tag

"""

__author__ =  'Jason Penney'
__version__ =  '0.5'

import os
import fnmatch
import eyeD3
from os.path import join, getsize, dirname, abspath
import sys

tagVersionToSet = eyeD3.ID3_V2_3
verbose = True # set to False to run silently

def convertFrames(tag,oldID,newID):
     """
     Convert frames of type `oldID`, or `TXXX` frames with
     description `oldID` to frames of type `newID`

     Return number of changed frames

     Keyword arguments:
     tag - the eyeD3.tag to act on
     oldID - frame id to match
     newID - frame id to convert to

     Example usage:
     >>> count = convertFrames(tag, 'XSOP', 'TSOP')

     """

     count = 0
     for frm in tag.frames[oldID]:
          frm.header.id = newID
          count = count + 1

     for frm in tag.frames["TXXX"]:
          if (frm.description == oldID):
               frm.header.id = newID
               frm.description = frm.text
               frm.text = ""
               count = count + 1

     return count

def fixTracks():
     """
     run `convertFrames` on all mp3 files found in the current directory
     (including subdirectories)

     """
     if verbose:
          print "processing files"
     for root, dirs, files in os.walk('.'):
          if verbose:
               print root

          fileList = fnmatch.filter(files,"*mp3")
          for name in fileList:
              update = 0
              # clean up path for cross platform use
              f = os.path.join(root, name)
              f = os.path.normcase(os.path.abspath(f))

              tag = eyeD3.Tag();
              test = None
              try:
                   test = tag.link(f)
              except:
                   print "error with tag:", f, " -- ", sys.exc_info()[0]
                   test = None
              if not test:
                   tag = None;

              if tag:
                   changed = 0
                   changed += convertFrames(tag,'XSOP','TSOP')
                   changed += convertFrames(tag,'XSOT','TSOT')
                   changed += convertFrames(tag,'XSOA','TSOA')
                   changed += convertFrames(tag,'ALBUMARTISTSORT','TSO2')
                   # only update if needed
                   if (changed > 0):
                        if verbose:
                             print "updating ",f
                        tag.update(tagVersionToSet)

if __name__ == '__main__':
     # Import Psyco if available
     try:
          import psyco
          psyco.full()

     except ImportError:
          pass
     fixTracks()
     sys.exit(0)
  1. Besides each computer, it’s directly available to my receiver, my TiVo, and a handful of portable devices []
  2. Also if these tags exist in a file, iTunes can update them, otherwise it will not create them inside an ID3v2.3 tag []
  3. Of course if you’re actually using the checks for something, then this may not be the best option for you []

One Response to “Fixing Up Sort Related Tags for iTunes”

  1. Unspecified Error: Learn by Doing | All the Billion Other Moments (Jason Penney) Says:

    [...] first contribution covers one of the many ways I use Python to bend iTunes to my will. Check it out2.Notes↑1 Seriously, Chris is everywhere. He’s just stealthy about it. If [...]

Leave a Reply