/
extendedDuplicateFinder.py
157 lines (136 loc) · 4.14 KB
/
extendedDuplicateFinder.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
from beets.plugins import BeetsPlugin
from beets.ui import Subcommand, print_obj
from beets.library import Item
####################
# Helper Functions #
####################
def force_unicode(s):
"""Forces returned string to be unicode
"""
if isinstance(s, str):
return s.decode("utf8")
return s
def check_key(key_list, items):
"""Creates dictionary which contains the matching attributes
(as the key) and a list of the concerned items (as the value)
"""
matches = {}
for i in items:
matches.setdefault(
"-".join(
[
str(i[key])
if i[key] == None or isinstance(
i[key], (int, long, float, complex)
)
else force_unicode(i[key])
for key in key_list
]
), []
).append(i)
return matches
def gen_keylist(opts):
"""Generates list of all Item() attributes
"""
return [
k
for k in Item().keys()
if (bool(opts["negate_all_options"]) ^ bool(opts[k])) or
opts["compare_all"]
]
def gen_parser(cmd):
"""Creates cmdline argument parser which contains each Item()
attribute and a couple of additional parameters
"""
cmd.parser.add_option(
'--delete',
dest = 'actually_delete_files',
action = 'store_true',
help = 'Not only list, but actually erase items '
'from database and disk'
)
cmd.parser.add_option(
'--negate',
dest = 'negate_all_options',
action = 'store_true',
help = 'Negate all passed options, e.g. "--title --negate" '
'would compare everything but the title '
'(has no effect with --all)'
)
cmd.parser.add_option(
'--all',
dest = 'compare_all',
action = 'store_true',
help = 'Compares all available options, i.e. only finds '
'exact matches (takes precedence over --negate)'
)
cmd.parser.add_option(
'-f',
'--output_format',
dest='output_format',
action='store',
type='string',
help='Print with custom format',
metavar='FORMAT'
)
cmd.parser.add_option(
'-c',
'--count',
dest='output_count',
action='store_true',
help='Count duplicate tracks or albums '
'instead of displaying all of them'
)
for k in Item().keys():
cmd.parser.add_option(
'--%s' % k,
dest = k,
action = 'store_true',
help = 'Check for duplicate %ss' % k
)
def remove_item(item, lib):
"""Erases specified item from database and disk
"""
lib.remove(item, delete = True, with_album = True)
def dupl_finder(lib, opts, args):
"""Lists and possibly deletes duplicates
"""
opts = vars(opts)
# handle special printing formats
if opts["output_format"]:
fmt = opts["output_format"]
else:
fmt = '$albumartist - $album - $title'
if opts["output_count"]:
fmt += ": ({0})"
key_list = gen_keylist(opts)
if len(key_list) == 0:
# no option set, setting default one
key_list = ['title', 'artist', 'album']
res = check_key(key_list, lib.items(query = args))
for key, match_list in res.iteritems():
num = len(match_list)
if num > 1:
# found duplicates
if opts["output_count"]:
print_obj(match_list[0], lib, fmt=fmt.format(num))
else:
for match in match_list:
print_obj(match, lib, fmt=fmt.format(num))
print ""
#######################
# Command Declaration #
#######################
find_command = Subcommand(
'find_duplicates',
help = 'Lists all duplicates for given query',
aliases = ["fdup"]
)
find_command.func = dupl_finder
gen_parser(find_command)
#####################
# Plugin Definition #
#####################
class ExtendedDuplicateFinder(BeetsPlugin):
def commands(self):
return [find_command]