-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathkill_dupes
executable file
·167 lines (146 loc) · 7.07 KB
/
kill_dupes
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
#!/usr/bin/env python3
import sys
from gmusicapi import Mobileclient
try:
import credentials
# credentials.py is in .gitignore and can be shared with other scripts
username = credentials.username
password = credentials.password
except ImportError:
username = 'username'
password = 'password'
try:
# optionally hardcode id to workaround https://github.com/simon-weber/gmusicapi/issues/408
android_id = credentials.android_id
except (NameError, AttributeError):
android_id = Mobileclient.FROM_MAC_ADDRESS
def map_track_duplication(tracks):
album_track_duplicate_map = {}
for track in tracks:
albumNorm = track['album'].lower()
titleNorm = track['title'].lower()
try:
trackNumberNorm = track['trackNumber']
except:
trackNumberNorm = 0
try:
discNumberNorm = track['discNumber']
except:
discNumberNorm = 0
if albumNorm not in album_track_duplicate_map:
album_track_duplicate_map.update({albumNorm: {}})
if titleNorm not in album_track_duplicate_map[albumNorm]:
album_track_duplicate_map[albumNorm].update({titleNorm: {}})
if trackNumberNorm not in album_track_duplicate_map[albumNorm][titleNorm]:
album_track_duplicate_map[albumNorm][titleNorm].update(
{trackNumberNorm: {}})
if discNumberNorm in album_track_duplicate_map[albumNorm][titleNorm][trackNumberNorm]:
album_track_duplicate_map[albumNorm][titleNorm][trackNumberNorm][discNumberNorm] += 1
else:
album_track_duplicate_map[albumNorm][titleNorm][trackNumberNorm][discNumberNorm] = 1
return album_track_duplicate_map
def sort_tracks_by_album(tracks):
tracks_by_album = {}
for track in tracks:
albumNorm = track['album'].lower()
if albumNorm not in tracks_by_album:
tracks_by_album[albumNorm] = []
tracks_by_album[albumNorm].append(track)
return tracks_by_album
def get_duplicate_tracks(all_tracks_by_album, album_track_duplicate_map):
duplicate_tracks = []
for album_title in album_track_duplicate_map:
for track_title in album_track_duplicate_map[album_title]:
for track_number in album_track_duplicate_map[album_title][track_title]:
for disc_number in album_track_duplicate_map[album_title][track_title][track_number]:
# As one is always added
duplicates = album_track_duplicate_map[album_title][track_title][track_number][disc_number] - 1
if duplicates > 0:
for album in all_tracks_by_album:
if album_title == album.lower():
for track in all_tracks_by_album[album]:
titleNorm = track['title'].lower()
if titleNorm == track_title:
try:
trackNumberNorm = track['trackNumber']
except:
trackNumberNorm = 0
if trackNumberNorm == track_number:
try:
discNumberNorm = track['discNumber']
except:
discNumberNorm = 0
if discNumberNorm == disc_number and duplicates > 0:
duplicate_tracks.append(track)
# PR Encode output in utf-8 to avoid errors when printing special characters not supported by default codeset
print(
(
"Queuing for removal: '"
+ track.get('title', 'No Track Title')
+ "' from album '"
+ track.get('album', 'None')
+ "' by '"
+ track.get('artist', 'None')
).encode('utf8')
)
all_tracks.remove(track)
duplicates -= 1
return duplicate_tracks
def query_yes_no(question, default="yes"):
"""Ask a yes/no question via input() and return their answer.
"question" is a string that is presented to the user.
"default" is the presumed answer if the user just hits <Enter>.
It must be "yes" (the default), "no" or None (meaning
an answer is required of the user).
The "answer" return value is one of "yes" or "no".
"""
valid = {"yes": True, "y": True, "ye": True,
"no": False, "n": False}
if default == None:
prompt = " [y/n] "
elif default == "yes":
prompt = " [Y/n] "
elif default == "no":
prompt = " [y/N] "
else:
raise ValueError("invalid default answer: '%s'" % default)
while True:
sys.stdout.write(question + prompt)
if sys.version_info[0] < 3:
choice = raw_input().lower()
else:
choice = input().lower()
if default is not None and choice == '':
return valid[default]
elif choice in valid:
return valid[choice]
else:
sys.stdout.write("Please respond with 'yes' or 'no' "
"(or 'y' or 'n').\n")
def get_track_ids(tracks):
track_ids = []
for track in tracks:
track_ids.append(track['id'])
return track_ids
api = Mobileclient()
api.__init__(True, True, True)
oauth_credentials = api.perform_oauth(username, password)
logged_in = api.oauth_login(api.FROM_MAC_ADDRESS, oauth_credentials)
if logged_in:
print("Successfully logged in. Beginning duplicate detection process.")
all_tracks = api.get_all_songs()
album_track_duplicate_map = map_track_duplication(all_tracks)
all_tracks_by_album = sort_tracks_by_album(all_tracks)
duplicate_tracks = get_duplicate_tracks(
all_tracks_by_album, album_track_duplicate_map)
duplicate_track_ids = get_track_ids(duplicate_tracks)
if len(duplicate_track_ids) > 0:
if query_yes_no("Found " + str(len(duplicate_track_ids)) + " duplicate tracks. Delete duplicates?", "no"):
deleted_track_ids = []
for track in duplicate_track_ids:
deleted_track_ids += api.delete_songs(track)
print("Successfully deleted " + str(len(deleted_track_ids)) + " of " +
str(len(duplicate_track_ids)) + " queued songs for removal.")
else:
print("I didn't find any duplicate tracks.")
print("Thank you!")