Skip to content

Instantly share code, notes, and snippets.

@asottile
Created November 2, 2018 03:53
Show Gist options
  • Save asottile/76a29a21f35b7f2a6f01b6d1c422bf8d to your computer and use it in GitHub Desktop.
Save asottile/76a29a21f35b7f2a6f01b6d1c422bf8d to your computer and use it in GitHub Desktop.
move itunes library
import os.path
import shutil
import urllib.parse
from xml.sax import make_parser
from xml.sax.saxutils import XMLFilterBase
from xml.sax.saxutils import XMLGenerator
DEST = 'E:/music'
class MoveFilenamesFilter(XMLFilterBase):
def __init__(self, *a):
super().__init__(*a)
self._all = set()
self._el = self._last_key = self._content = ''
def startElement(self, name, attrs):
self._el = name
self._content = ''
super().startElement(name, attrs)
def endElement(self, name):
el, self._el = self._el, ''
if el == 'key':
self._last_key = self._content
super().characters(self._content)
elif el == 'string':
if self._last_key == 'Location':
parsed = urllib.parse.urlparse(self._content)
old_path = urllib.parse.unquote(parsed.path.lstrip('/'))
# uncomment to actually move
# shutil.copy(old_path, DEST)
# os.remove(old_path)
new_path = f'{DEST}/{os.path.basename(old_path)}'
if new_path in self._all:
raise AssertionError(new_path)
self._all.add(new_path)
parsed = parsed._replace(path=new_path)
super().characters(urllib.parse.urlunparse(parsed))
else:
super().characters(self._content)
super().endElement(name)
def characters(self, content):
if self._el in {'key', 'string'}:
self._content += content
else:
super().characters(content)
class PListDoctype(XMLGenerator):
def startDocument(self):
super().startDocument()
self._write('<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n')
def main():
reader = MoveFilenamesFilter(make_parser())
with open('itl_new.xml', 'w', encoding='UTF-8', newline='\r\n') as f:
handler = PListDoctype(f, encoding='UTF-8', short_empty_elements=True)
reader.setContentHandler(handler)
reader.parse('iTunes Music Library.xml')
if __name__ == '__main__':
exit(main())
@asottile
Copy link
Author

asottile commented Nov 2, 2018

Usage:

  1. turn on xml mode for itunes
  2. close itunes
  3. make a backup of both the .xml and the .itl file in your itunes directory
  4. copy iTunes Music Library.xml to a temporary directory
  5. change the DEST variable
  6. run the script (this'll be dry-run mode, with dupe checking)
  7. uncomment the mutation bits
  8. run the script
  9. move the .itl file out of the way in your music directory
  10. start itunes
  11. "File -> Library -> Import Playlist" and select itl_new.xml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment