|
1 | 1 | #!/usr/bin/env python2.7 |
| 2 | +# |
| 3 | +# music-organizer.py |
| 4 | +# Brandon Amos <http://bamos.io/> |
| 5 | +# 2014.4.19 |
2 | 6 |
|
| 7 | +import argparse |
3 | 8 | import os |
4 | 9 | import re |
5 | 10 | import shutil |
6 | 11 | import sys |
7 | 12 | from mutagen.easyid3 import EasyID3 |
8 | 13 |
|
9 | | -emptyChars = re.compile(r"[(),.'\\\?\#]") |
| 14 | +parser = argparse.ArgumentParser( |
| 15 | + description='''Organizes a music collection using tag information. |
| 16 | + The directory format is that the music collection consists of |
| 17 | + artist subdirectories, and there are 2 modes to operate on |
| 18 | + the entire collection or a single artist. |
| 19 | + All names are made lowercase and separated by dashes for easier |
| 20 | + navigation in a Linux filesystem.''' |
| 21 | +) |
| 22 | +parser.add_argument('--delete-conflicts', action='store_true', |
| 23 | + dest='delete_conflicts', |
| 24 | + help='''If an artist has duplicate tracks with the same name, |
| 25 | + delete them. Note this might always be best in case an artist |
| 26 | + has multiple versions. To keep multiple versions, |
| 27 | + fix the tag information.''') |
| 28 | +parser.add_argument('--collection', action='store_true', |
| 29 | + help='TODO') |
| 30 | +parser.add_argument('--artist', action='store_true', |
| 31 | + help='TODO') |
| 32 | +args = parser.parse_args() |
| 33 | + |
| 34 | +if args.collection and args.artist: |
| 35 | + print("Error: Only provide 1 of '--collection' or '--artist'.") |
| 36 | + sys.exit(-1) |
| 37 | +elif not (args.collection or args.artist): |
| 38 | + print("Error: Mode '--collection' or '--artist' not provided.") |
| 39 | + sys.exit(-1) |
| 40 | + |
| 41 | +# Maps a string such as 'The Beatles' to 'the-beatles'. |
10 | 42 | def toNeat(s): |
11 | | - s = s.lower().replace(" ","-").replace("&","and").replace("*","-") |
12 | | - s = emptyChars.sub("", s) |
| 43 | + s = s.lower().replace("&","and") |
| 44 | + s = re.sub(r"[()\[\],.'\\\?\#/\!]", "", s) |
| 45 | + s = re.sub(r"[ \*\_]", "-", s) |
| 46 | + s = re.sub("-+", "-", s) |
13 | 47 | search = re.search("[^0-9a-z\-]", s) |
14 | 48 | if search: |
15 | 49 | print("Error: Unrecognized character in '" + s + "'") |
16 | 50 | sys.exit(-42) |
17 | 51 | return s |
18 | 52 |
|
19 | | -artists = set() |
20 | | -valid = {"yes":True, "y":True, "no":False, "n":False} |
21 | | -for dirname, dirnames, filenames in os.walk('.'): |
22 | | - # Make sure there aren't a lot of different artists |
23 | | - # in case this was called from the wrong directory. |
24 | | - for filename in filenames: |
25 | | - try: |
26 | | - audio = EasyID3(os.path.join(dirname, filename)) |
27 | | - artist = audio['artist'][0].decode() |
28 | | - artists.add(artist) |
29 | | - except: |
30 | | - pass |
31 | | - |
32 | | -if len(artists) > 2: |
33 | | - while True: |
34 | | - print("Warning: More than 2 artists found.") |
35 | | - print("This will move all songs to the current directory.") |
36 | | - print("Continue? yes/no") |
37 | | - choice = raw_input().lower() |
38 | | - if choice in valid: |
39 | | - if valid[choice]: break |
40 | | - else: |
41 | | - print("Exiting.") |
42 | | - sys.exit(-1) |
43 | | - |
44 | | -delete_dirs = [] |
45 | | -for dirname, dirnames, filenames in os.walk('.'): |
46 | | - # Move all the files to the root directory. |
47 | | - for filename in filenames: |
48 | | - ext = os.path.splitext(filename)[1] |
49 | | - if ext == ".mp3": |
50 | | - fullPath = os.path.join(dirname, filename) |
51 | | - print("file: " + str(fullPath)) |
52 | | - |
| 53 | +def artist(): |
| 54 | + artists = set() |
| 55 | + valid = {"yes":True, "y":True, "no":False, "n":False} |
| 56 | + for dirname, dirnames, filenames in os.walk('.'): |
| 57 | + # Make sure there aren't a lot of different artists |
| 58 | + # in case this was called from the wrong directory. |
| 59 | + for filename in filenames: |
53 | 60 | try: |
54 | | - audio = EasyID3(fullPath) |
55 | | - title = audio['title'][0].decode() |
56 | | - print(" title: " + title) |
57 | | - except: title = None |
| 61 | + audio = EasyID3(os.path.join(dirname, filename)) |
| 62 | + artist = audio['artist'][0].decode() |
| 63 | + artists.add(artist) |
| 64 | + except: |
| 65 | + pass |
58 | 66 |
|
59 | | - if not title: |
60 | | - print("Error: title not found for '" + filename + "'") |
61 | | - sys.exit(-42) |
| 67 | + if len(artists) > 2: |
| 68 | + while True: |
| 69 | + print("Warning: More than 2 artists found.") |
| 70 | + print("This will move all songs to the current directory.") |
| 71 | + print("Continue? yes/no") |
| 72 | + choice = raw_input().lower() |
| 73 | + if choice in valid: |
| 74 | + if valid[choice]: break |
| 75 | + else: |
| 76 | + print("Exiting.") |
| 77 | + sys.exit(-1) |
62 | 78 |
|
63 | | - neatTitle = toNeat(title) |
64 | | - print(" neat-title: " + neatTitle) |
| 79 | + delete_dirs = [] |
| 80 | + for dirname, dirnames, filenames in os.walk('.'): |
| 81 | + # Move all the files to the root directory. |
| 82 | + for filename in filenames: |
| 83 | + ext = os.path.splitext(filename)[1] |
| 84 | + if ext == ".mp3": |
| 85 | + fullPath = os.path.join(dirname, filename) |
| 86 | + print("file: " + str(fullPath)) |
65 | 87 |
|
66 | | - newFullPath = os.path.join(".", neatTitle + ext) # Remove subdirectories. |
67 | | - print(" newFullPath: " + newFullPath) |
| 88 | + try: |
| 89 | + audio = EasyID3(fullPath) |
| 90 | + title = audio['title'][0].decode() |
| 91 | + print(" title: " + title) |
| 92 | + except: title = None |
68 | 93 |
|
69 | | - if newFullPath != fullPath: |
70 | | - if os.path.isfile(newFullPath): |
71 | | - print("Error: File exists: '" + newFullPath + "'") |
| 94 | + if not title: |
| 95 | + print("Error: title not found for '" + filename + "'") |
72 | 96 | sys.exit(-42) |
73 | 97 |
|
74 | | - os.rename(fullPath, newFullPath) |
75 | | - os.chmod(newFullPath, 0644) |
76 | | - elif ext == ".pdf": |
77 | | - pass |
78 | | - else: |
79 | | - print("Error: Unrecognized file extension in '" + filename + "'") |
80 | | - sys.exit(-42) |
| 98 | + neatTitle = toNeat(title) |
| 99 | + print(" neat-title: " + neatTitle) |
| 100 | + |
| 101 | + newFullPath = os.path.join(".", neatTitle + ext) |
| 102 | + print(" newFullPath: " + newFullPath) |
| 103 | + |
| 104 | + if newFullPath != fullPath: |
| 105 | + if os.path.isfile(newFullPath): |
| 106 | + if args.delete_conflicts: |
| 107 | + os.remove(fullPath) |
| 108 | + print("File exists: '" + newFullPath + "'") |
| 109 | + print("Deleted: '" + fullPath + "'") |
| 110 | + else: |
| 111 | + print("Error: File exists: '" + newFullPath + "'") |
| 112 | + sys.exit(-42) |
| 113 | + else: |
| 114 | + os.rename(fullPath, newFullPath) |
| 115 | + os.chmod(newFullPath, 0644) |
| 116 | + elif ext == ".pdf": |
| 117 | + pass |
| 118 | + else: |
| 119 | + print("Error: Unrecognized file extension in '" + filename + "'") |
| 120 | + sys.exit(-42) |
| 121 | + |
| 122 | + # Delete all subdirectories. |
| 123 | + for subdirname in dirnames: |
| 124 | + delete_dirs.append(subdirname) |
81 | 125 |
|
82 | | - # Delete all subdirectories. |
83 | | - for subdirname in dirnames: |
84 | | - delete_dirs.append(subdirname) |
| 126 | + for d in delete_dirs: |
| 127 | + shutil.rmtree(d,ignore_errors=True) |
85 | 128 |
|
86 | | -for d in delete_dirs: |
87 | | - shutil.rmtree(d,ignore_errors=True) |
| 129 | + print("\nComplete!") |
88 | 130 |
|
89 | | -print("\nComplete!") |
| 131 | +if args.artist: |
| 132 | + artist() |
0 commit comments