-
Notifications
You must be signed in to change notification settings - Fork 0
/
sortMusicFiles.py
287 lines (260 loc) · 14.3 KB
/
sortMusicFiles.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#! /
from __future__ import print_function
import unicodedata as ud
import song as Song
import os
import mutagen
import shutil
# import easygui as eg
try:
from Tkinter import Tk
from tkFileDialog import askdirectory
except:
pass
class SortMusicFiles:
def __init__(self):
self.musicFilesLocation = ""
self.outputFilesLocation = ""
self.Songs = []
self.firstSortingCriteria = "albumartist"
self.secondSortingCriteria = "album"
self.thirdSortingCriteria = ""
def resetObjectToDefault(self):
"""Used only for testing, so we don't have to create a bunch of separate objects"""
self.__init__()
def setMusicFilesLocation(self, fileLoc):
self.musicFilesLocation = fileLoc
def setMusicFilesLocationFromUserInput(self):
"""This may be updated in the future with a GUI"""
try:
Tk().withdraw()
location = askdirectory()
except:
doesPathExist = 0
while not doesPathExist:
location = raw_input("Enter path to your music:\t")
if os._exists(location, os.R_OK): # can we `R`ead from the path?
doesPathExist = 1
self.setMusicFilesLocation(location)
# self.setMusicFilesLocation(self.removeBadSymbolsInPathName(raw_input("What is the path where your music is located?\t")))
def getMusicFilesLocation(self):
return self.musicFilesLocation
def setOutputFilesLocation(self, fileLoc):
self.outputFilesLocation = fileLoc
def setOutputFilesLocationFromUserInput(self):
"""This may be updated in the future with a GUI"""
try:
Tk().withdraw()
location = askdirectory()
except:
doesPathExist = 0
while not doesPathExist:
location = raw_input("Enter path to where you want your music to go:\t")
if os._exists(location, os.W_OK): # can we `W`rite to the path?
doesPathExist = 1
self.setOutputFilesLocation(location)
def getOutputFilesLocation(self):
return self.outputFilesLocation
def findSongs(self):
"""Searches musicFilesLocation for music files, then puts it in Songs list"""
if self.musicFilesLocation == "":
self.setMusicFilesLocationFromUserInput()
# raise IOError
musicFileExtensions = ['.3gp', '.aa', '.aac', '.aax', '.act', '.aiff', '.amr',
'.ape', '.au', '.awb', '.dct', '.dss', '.dvf', '.flac',
'.gsm', '.iklax', '.ivs', '.m4a', '.m4b', '.m4p', '.mmf',
'.mp3', '.mpc', '.msv', '.ogg', '.oga', '.mogg', '.opus',
'.ra', '.rm', '.raw', '.sln', '.tta', '.vox', '.wav',
'.wma', '.wv', '.webm']
for thing in os.walk(self.musicFilesLocation):
for ext in musicFileExtensions:
# thing is tuple: (pathname, dirs, files)
# we want the files. os.walk then recurses into any dirs, so we catch files in there too
for possibleMusicFile in thing[2]:
if ext in possibleMusicFile:
newSong = Song.Song()
metadata = mutagen.File(thing[0] +"/"+ possibleMusicFile)
newSong.setFileName(possibleMusicFile)
newSong.setFileLocation(thing[0] + "/") #self.musicFilesLocation + "/")
newSong.getSongInfoFromFile()
# try:
# newSong.setTitle(metadata["title"][0])
# except KeyError as e:
# print(e)
# try:
# newSong.setArtist(metadata["artist"][0])
# except KeyError as e:
# print(e)
# try:
# newSong.setAlbum(metadata["album"][0])
# except KeyError as e:
# print(e)
# try:
# newSong.setAlbumArtist(metadata["albumartist"][0])
# except KeyError as e:
# print(e)
# try:
# newSong.setDate(metadata["date"][0])
# except KeyError as e:
# print(e)
# try:
# newSong.setTrackNumber(metadata["tracknumber"][0])
# except KeyError as e:
# print(e)
# try:
# newSong.setTrackTotal(metadata["tracktotal"][0])
# except KeyError as e:
# print(e)
self.Songs.append(newSong)
def getSortingCriteriaDataByCriteriaPriority(self, song, priority):
"""Gets data from Song object according to the music file sorter's first
sorting criteria"""
# switch statement allows us to obtain the appropriate data, depending on
# what the sorting criteria is set to. Not executing the functions (that's
# why the parentheses at the end are omitted) should, in theory, reduce some
# overhead of unnecessary function calls, but I don't know how much work that
# is compared with doing what I did below
switchStatement = {"artist": song.getArtist, "album": song.getAlbum,
"albumartist": song.getAlbumArtist, "title": song.getTitle,
"date": song.getDate, "tracknumber": song.getTrackNumber,
"tracktotal": song.getTrackTotal}
# use a list, since we'll pass in an int as the priority
criteriaByPriority = [self.firstSortingCriteria, self.secondSortingCriteria,
self.thirdSortingCriteria]
# since some files don't have album artist metadata, but nearly all have artist metadata, we're first trying
# to sort by album artist to prevent songs featuring other artists from being separated from the rest of
# an album produced without that featured artist
if (criteriaByPriority[priority] == "albumartist" and
(switchStatement[criteriaByPriority[priority]]() == "No albumartist info")):
# print("No album artist info, using artist info instead")
return switchStatement["artist"]()
return switchStatement[criteriaByPriority[priority]]()
def moveSong(self, song):
"""Moves a single song. Makes sure not to create a folder if one by the same
name already exists"""
# TODO: split these lines up into separate try-excepts, because it is possible
# TODO: that it will succeed at making the dirs but fail at moving the file,
# TODO: and the code as it currently is would yield errors on the later
# TODO: try-excepts, because the directory would already exist
if self.thirdSortingCriteria != "":
currentLocation = song.getFileLocation() + song.getFileName()
destination = (self.outputFilesLocation + "/" +
self.getSortingCriteriaDataByCriteriaPriority(song,0) + "/" +
self.getSortingCriteriaDataByCriteriaPriority(song,1) + "/" +
self.getSortingCriteriaDataByCriteriaPriority(song,2))
if os.path.exists(destination):
shutil.move(currentLocation, destination)
else:
os.makedirs(destination)
shutil.move(currentLocation, destination)
else:
# print("Song Artist:\t",song.getArtist())
currentLocation = song.getFileLocation() + song.getFileName()
destination = (self.outputFilesLocation + "/" +
self.getSortingCriteriaDataByCriteriaPriority(song, 0) + "/" +
self.getSortingCriteriaDataByCriteriaPriority(song, 1))
if os.path.exists(destination):
try:
shutil.move(currentLocation, destination)
except Exception as e:
print("Current file location:\t", currentLocation)
print("File destination:\t", destination)
print(e)
else:
try:
os.makedirs(destination)
except Exception as e:
print("Current file location:\t", currentLocation)
print("File destination:\t", destination)
print(e)
try:
shutil.move(currentLocation, destination)
except Exception as e:
print("Current file location:\t", currentLocation)
print("File destination:\t", destination)
print(e)
# shutil.move(song.getFileLocation() + song.getFileName(), self.musicFilesLocation + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,0) + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,1) + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,2))
# shutil.move(song.getFileLocation() + song.getFileName(), self.musicFilesLocation + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song, 0) + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song, 1))
# else:
# # print(e)
# shutil.move(song.getFileLocation() + song.getFileName(), self.musicFilesLocation + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song, 0) + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song, 1))
#
# # if all directories have been made successfully
# if os.access(self.musicFilesLocation + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,0) + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,1) + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,2), os.F_OK):
# shutil.move(song.getFileLocation() + song.getFileName(),
# self.musicFilesLocation + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,0) + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,1) + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,2))
#
# # if only first 2 were created
# elif os.access(self.musicFilesLocation + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,0) + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,1), os.F_OK):
# shutil.move(song.getFileLocation() + song.getFileName(),
# self.musicFilesLocation + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,0) + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,1))
#
# # if only first directory was created (shouldn't happen, but just in case)
# elif os.access(self.musicFilesLocation + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,0), os.F_OK):
# shutil.move(song.getFileLocation() + song.getFileName(),
# self.musicFilesLocation + "/" +
# self.getSortingCriteriaDataByCriteriaPriority(song,0))
#
# else:
# print("Something went dreadfully amiss, and we were unable to relocate your song.")
def sortFiles(self):
"""Actually handles the sorting of the music files"""
self.findSongs()
if self.getOutputFilesLocation() == "":
self.setOutputFilesLocationFromUserInput()
numSongs = len(self.Songs)
onePercentOfNumSongs = (numSongs / 100) + 1 # add 1 to avoid divide by 0 errors
counter = 0
print("Music Location:\t",self.getMusicFilesLocation())
print("Output Location:\t",self.getOutputFilesLocation())
# print("songs:\t",self.Songs)
print("Number of songs:\t", numSongs)
print("Progress:\t",end="")
for song in self.Songs:
if counter % onePercentOfNumSongs == 0:
print("-",end="")
self.moveSong(song)
counter += 1
self.cleanup()
def cleanup(self):
"""Asks user if they want to remove empty directories, and does it if they wish"""
# TODO: prompt the user to decide this before running
if (raw_input("Do you want to remove excess folders leftover from your before sorting your music?")
in ["y","yes","Y","Yes"]):
# set topdown to False so that we go to deepest nested dirs first; that way, we can delete
# any empty dirs, and then go up to the containing folder, which may then be empty, and
# delete it if necessary
for thing in os.walk(self.musicFilesLocation, topdown=False):
# thing is tuple: (pathname, dirs, files)
if ((thing[1] == []) and (thing[2] == [])): # if there are no files or dirs in this dir
os.rmdir(thing[0])
def removeBadSymbolsInPathName(self, path):
"""Removes symbols incompatible Windows's file system"""
prohibitedSymbolsInWindowsPaths = ["\\", "/", ":", "\"", "*", "?", "<", ">", "|"]
substituteSymbolsInWindowsPaths = ["--", "--", ";", "'", "_", "_", "(", ")", "_"]
if type(path) == type(u'unicodeStr'):
path = ud.normalize("NFKD", path).encode("ascii", "ignore")
# nameStr = repr(nameStr)
for idx, badSymbol in enumerate(prohibitedSymbolsInWindowsPaths):
if badSymbol in path:
path = str(path).replace(badSymbol, substituteSymbolsInWindowsPaths[idx])
return path
# def getSongInfoAndCreateSongObject(self):