@@ -42,6 +42,7 @@ def __init__(self):
42
42
'format' : '' ,
43
43
'full' : False ,
44
44
'keys' : [],
45
+ 'merge' : False ,
45
46
'move' : '' ,
46
47
'path' : False ,
47
48
'tiebreak' : {},
@@ -81,6 +82,10 @@ def __init__(self):
81
82
callback = vararg_callback ,
82
83
help = 'report duplicates based on keys' )
83
84
85
+ self ._command .parser .add_option ('-M' , '--merge' , dest = 'merge' ,
86
+ action = 'store_true' ,
87
+ help = 'merge duplicate items' )
88
+
84
89
self ._command .parser .add_option ('-m' , '--move' , dest = 'move' ,
85
90
action = 'store' , metavar = 'DEST' ,
86
91
help = 'move items to dest' )
@@ -108,6 +113,7 @@ def _dup(lib, opts, args):
108
113
fmt = self .config ['format' ].get (str )
109
114
full = self .config ['full' ].get (bool )
110
115
keys = self .config ['keys' ].get (list )
116
+ merge = self .config ['merge' ].get (bool )
111
117
move = self .config ['move' ].get (str )
112
118
path = self .config ['path' ].get (bool )
113
119
tiebreak = self .config ['tiebreak' ].get (dict )
@@ -143,10 +149,11 @@ def _dup(lib, opts, args):
143
149
keys = keys ,
144
150
full = full ,
145
151
strict = strict ,
146
- tiebreak = tiebreak ):
152
+ tiebreak = tiebreak ,
153
+ merge = merge ):
147
154
if obj_id : # Skip empty IDs.
148
155
for o in objs :
149
- self ._process_item (o , lib ,
156
+ self ._process_item (o ,
150
157
copy = copy ,
151
158
move = move ,
152
159
delete = delete ,
@@ -156,10 +163,11 @@ def _dup(lib, opts, args):
156
163
self ._command .func = _dup
157
164
return [self ._command ]
158
165
159
- def _process_item (self , item , lib , copy = False , move = False , delete = False ,
166
+ def _process_item (self , item , copy = False , move = False , delete = False ,
160
167
tag = False , fmt = '' ):
161
- """Process Item `item` in `lib` .
168
+ """Process Item `item`.
162
169
"""
170
+ print_ (format (item , fmt ))
163
171
if copy :
164
172
item .move (basedir = copy , copy = True )
165
173
item .store ()
@@ -175,7 +183,6 @@ def _process_item(self, item, lib, copy=False, move=False, delete=False,
175
183
raise UserError ('%s: can\' t parse k=v tag: %s' % (PLUGIN , tag ))
176
184
setattr (item , k , v )
177
185
item .store ()
178
- print_ (format (item , fmt ))
179
186
180
187
def _checksum (self , item , prog ):
181
188
"""Run external `prog` on file path associated with `item`, cache
@@ -249,12 +256,66 @@ def _order(self, objs, tiebreak=None):
249
256
250
257
return sorted (objs , key = key , reverse = True )
251
258
252
- def _duplicates (self , objs , keys , full , strict , tiebreak ):
259
+ def _merge_items (self , objs ):
260
+ """Merge Item objs by copying missing fields from items in the tail to
261
+ the head item.
262
+
263
+ Return same number of items, with the head item modified.
264
+ """
265
+ fields = [f for sublist in Item .get_fields () for f in sublist ]
266
+ for f in fields :
267
+ for o in objs [1 :]:
268
+ if getattr (objs [0 ], f , None ) in (None , '' ):
269
+ value = getattr (o , f , None )
270
+ if value :
271
+ self ._log .debug (u'key {0} on item {1} is null '
272
+ 'or empty: setting from item {2}' ,
273
+ f , displayable_path (objs [0 ].path ),
274
+ displayable_path (o .path ))
275
+ setattr (objs [0 ], f , value )
276
+ objs [0 ].store ()
277
+ break
278
+ return objs
279
+
280
+ def _merge_albums (self , objs ):
281
+ """Merge Album objs by copying missing items from albums in the tail
282
+ to the head album.
283
+
284
+ Return same number of albums, with the head album modified."""
285
+ ids = [i .mb_trackid for i in objs [0 ].items ()]
286
+ for o in objs [1 :]:
287
+ for i in o .items ():
288
+ if i .mb_trackid not in ids :
289
+ missing = Item .from_path (i .path )
290
+ missing .album_id = objs [0 ].id
291
+ missing .add (i ._db )
292
+ self ._log .debug (u'item {0} missing from album {1}:'
293
+ ' merging from {2} into {3}' ,
294
+ missing ,
295
+ objs [0 ],
296
+ displayable_path (o .path ),
297
+ displayable_path (missing .destination ()))
298
+ missing .move (copy = True )
299
+ return objs
300
+
301
+ def _merge (self , objs ):
302
+ """Merge duplicate items. See ``_merge_items`` and ``_merge_albums``
303
+ for the relevant strategies.
304
+ """
305
+ kind = Item if all (isinstance (o , Item ) for o in objs ) else Album
306
+ if kind is Item :
307
+ objs = self ._merge_items (objs )
308
+ else :
309
+ objs = self ._merge_albums (objs )
310
+ return objs
311
+
312
+ def _duplicates (self , objs , keys , full , strict , tiebreak , merge ):
253
313
"""Generate triples of keys, duplicate counts, and constituent objects.
254
314
"""
255
315
offset = 0 if full else 1
256
316
for k , objs in self ._group_by (objs , keys , strict ).iteritems ():
257
317
if len (objs ) > 1 :
258
- yield (k ,
259
- len (objs ) - offset ,
260
- self ._order (objs , tiebreak )[offset :])
318
+ objs = self ._order (objs , tiebreak )
319
+ if merge :
320
+ objs = self ._merge (objs )
321
+ yield (k , len (objs ) - offset , objs [offset :])
0 commit comments