comparison itunes2mp3.py @ 1:0ca90a90e723

Complete rework to make MP3 CDs from iTunes plist (XML) playlists. Use ffmpeg as it doesn't barf on long file names or some m4as (like faad) nor does it display unwanted album images and hang wasting CPU (mplayer). Shuffle now creates a directory and fills it with hardlinks in random order.
author Daniel O'Connor <darius@dons.net.au>
date Thu, 13 Jun 2013 00:16:23 +0930
parents 7ca49dff763a
children 1681a628ceb4
comparison
equal deleted inserted replaced
0:7ca49dff763a 1:0ca90a90e723
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 2
3 import errno 3 import errno
4 import glob 4 import glob
5 import os 5 import os
6 import plistlib
6 import random 7 import random
7 import shutil 8 import shutil
8 import subprocess 9 import subprocess
10 import sys
11 import urllib2
9 12
10 def compress(srcfile, destdir): 13 def compress(srcfile, destdir):
11 f = file(srcfile, 'rU') 14 plist = plistlib.readPlist(srcfile)
12 for line in f: 15 playlist = plist['Playlists'][0]
13 s = line.strip().split('\t')[-1] 16
14 path = reduce(lambda a, b: a + '/' + b, s.split(':')[1:], '') 17 failures = []
18 for item in playlist['Playlist Items']:
19 track = plist['Tracks'][str(item['Track ID'])]
20 if 'Location' not in track:
21 print "No location for " + track['Name']
22 continue
23
24 location = track['Location']
25
26 path = urllib2.unquote(urllib2.urlparse.urlparse(location).path)
15 print "Path is " + path 27 print "Path is " + path
28
16 destmp3 = destdir + '/' + path.split('/')[-1][:-3] + 'mp3' 29 destmp3 = destdir + '/' + path.split('/')[-1][:-3] + 'mp3'
17 if os.path.isfile(destmp3): 30 if os.path.isfile(destmp3):
18 print "Skipping" 31 print "Already done"
19 continue 32 continue
20 33
34 devnull = file('/dev/null', 'w')
21 if path.lower().endswith('.mp3'): 35 if path.lower().endswith('.mp3'):
22 shutil.copy(path, destmp3) 36 shutil.copy(path, destmp3)
23 elif path.lower().endswith('.m4a'): 37 elif path.lower().endswith('.m4a'):
24 faad = subprocess.Popen(['faad', '-qw', path], stdout = subprocess.PIPE) 38 ffmpeg = subprocess.Popen(['ffmpeg', '-y', '-v', 'quiet', '-b', '160', '-i', path, destmp3])
25 lame = subprocess.Popen(['lame', '--abr', '160', '-', destmp3], stdin = faad.stdout) 39 rtn = ffmpeg.wait()
26 faad.stdout.close() # So lame will get SIGPIPE 40
27 rtn = lame.communicate() 41 if rtn != 0:
28 print "Returned " + str(rtn) 42 print "Failed to convert %s, return code %d" % (path, rtn)
43 failures.append(path)
44 try:
45 os.unlink(destmp3)
46 except OSError, e:
47 if e.errno != 2:
48 raise e
29 else: 49 else:
30 print "Don't know how to convert " + path 50 print "Don't know how to convert " + path
51 if len(failures) > 0:
52 print
53 print "Failures:"
54 for f in failures:
55 print f
31 56
32 def shuffle(srcdir): 57 def shuffle(srcdir):
58 try:
59 os.mkdir(srcdir + '/burn')
60 except OSError, e:
61 if e.errno != 17:
62 raise e
33 files = glob.glob(srcdir + '/*.mp3') 63 files = glob.glob(srcdir + '/*.mp3')
34 random.shuffle(files) 64 random.shuffle(files)
35 for i in range(6): 65
36 destdir = srcdir + '/CD%02d' % (i + 1) 66 i = 0
37 try: 67 for f in files:
38 os.mkdir(destdir) 68 dest = srcdir + '/burn/%03d - %s' % (i, f.split('/')[-1])
39 except OSError, e: 69 i += 1
40 if e.errno == errno.EEXIST: 70 os.link(f, dest)
41 pass
42 for j in range(99):
43 try:
44 shutil.move(files.pop(), destdir)
45 except IndexError, e:
46 break
47 print "Ran out of space, %d left" % (len(files))
48 71
49 if __name__ == "__main__": 72 if __name__ == "__main__":
50 srcfile = 'Stuff I like.txt' 73 if len(sys.argv) != 2:
51 destdir = '/tmp/music' 74 print "Bad usage"
52 compress(srcfile, destdir) 75 print "%s playlist.xml destdir"
53 shuffle(destdir) 76 sys.exit(1)
77
78 compress(sys.argv[1], sys.argv[2])
79 shuffle(sys.argv[2])