def test_album_artist_overriden_by_nonempty_track_artist(self): my_info = copy.deepcopy(self.info) my_info.tracks[0].artist = 'artist1!' my_info.tracks[1].artist = 'artist2!' autotag.apply_metadata(self.items, my_info) self.assertEqual(self.items[0].artist, 'artist1!') self.assertEqual(self.items[1].artist, 'artist2!')
def test_album_artist_overriden_by_nonempty_track_artist(self): my_info = copy.deepcopy(self.info) my_info.tracks[0].artist = "artist1!" my_info.tracks[1].artist = "artist2!" autotag.apply_metadata(self.items, my_info) self.assertEqual(self.items[0].artist, "artist1!") self.assertEqual(self.items[1].artist, "artist2!")
def write_metadata_changes(self): assert self.is_completed, "Processing is not finished for task {}".format( self.id) apply_metadata(self._album_info_object, self._track_mapping_object) for item in self: item.write()
def _apply(self, info=None, per_disc_numbering=False): info = info or self.info mapping = {} for i, t in zip(self.items, info.tracks): mapping[i] = t config['per_disc_numbering'] = per_disc_numbering autotag.apply_metadata(info, mapping)
def test_mb_albumid_and_artistid_applied(self): autotag.apply_metadata(self.items, self.info) for item in self.items: self.assertEqual(item.mb_albumid, '7edb51cb-77d6-4416-a23c-3a8c2994a2c7') self.assertEqual(item.mb_artistid, 'a6623d39-2d8e-4f70-8242-0a9553b91e50')
def albums(self, lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for albums matched by query and their items. """ # Process matching albums. for album in lib.albums(query): # Do we have a valid Beatport album? items = self.get_album_tracks(album) if not items: continue # Get the Beatport album information. albuminfo = self.beatport_plugin.album_for_id(album.mb_albumid) if not albuminfo: self._log.info( 'Release ID {} not found for album {}', album.mb_albumid, album, ) continue beatport_trackid_to_trackinfo = { track.track_id: track for track in albuminfo.tracks } library_trackid_to_item = { int(item.mb_trackid): item for item in items } item_to_trackinfo = { item: beatport_trackid_to_trackinfo[track_id] for track_id, item in library_trackid_to_item.items() } self._log.info('applying changes to {}', album) with lib.transaction(): autotag.apply_metadata(albuminfo, item_to_trackinfo) changed = False # Find any changed item to apply Beatport changes to album. any_changed_item = items[0] for item in items: item_changed = ui.show_model_changes(item) changed |= item_changed if item_changed: any_changed_item = item apply_item_changes(lib, item, move, pretend, write) if pretend or not changed: continue # Update album structure to reflect an item in it. for key in library.Album.item_keys: album[key] = any_changed_item[key] album.store() # Move album art (and any inconsistent items). if move and lib.directory in util.ancestry(items[0].path): self._log.debug('moving album {}', album) album.move()
def test_album_artist_overriden_by_nonempty_track_artist(self): my_info = dict(self.info) my_info['tracks'] = [dict(t) for t in self.info['tracks']] my_info['tracks'][0]['artist'] = 'artist1!' my_info['tracks'][1]['artist'] = 'artist2!' autotag.apply_metadata(self.items, my_info) self.assertEqual(self.items[0].artist, 'artist1!') self.assertEqual(self.items[1].artist, 'artist2!')
def apply_choices(lib, copy, write, art, delete, progress): """A coroutine for applying changes to albums during the autotag process. The parameters to the generator control the behavior of the import. The coroutine accepts (items, info) pairs and yields nothing. items the set of Items to import; info is either a candidate info dictionary or CHOICE_ASIS. """ lib = _reopen_lib(lib) while True: # Get next chunk of work. toppath, path, items, info = yield # Check for "path finished" message. if path is DONE_SENTINEL: if progress: # Mark path as complete. progress_set(toppath, None) continue # Only process the items if info is not None (indicating a # skip). if info is not None: # Change metadata, move, and copy. if info is not CHOICE_ASIS: autotag.apply_metadata(items, info) if copy and delete: old_paths = [os.path.realpath(item.path) for item in items] for item in items: if copy: item.move(lib, True) if write and info is not CHOICE_ASIS: item.write() # Add items to library. We consolidate this at the end to avoid # locking while we do the copying and tag updates. albuminfo = lib.add_album(items, infer_aa = (info is CHOICE_ASIS)) # Get album art if requested. if art and info is not CHOICE_ASIS: artpath = beets.autotag.art.art_for_album(info) if artpath: albuminfo.set_art(artpath) # Write the database after each album. lib.save() # Finally, delete old files. if copy and delete: new_paths = [os.path.realpath(item.path) for item in items] for old_path in old_paths: # Only delete files that were actually moved. if old_path not in new_paths: os.remove(library._syspath(old_path)) # Update progress. if progress: progress_set(toppath, path)
def test_mb_albumartistid_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].mb_albumartistid, '89ad4ac3-39f7-470e-963a-56509c546377') self.assertEqual(self.items[1].mb_albumartistid, '89ad4ac3-39f7-470e-963a-56509c546377') self.assertEqual(self.items[0].mb_artistid, 'a05686fc-9db2-4c23-b99e-77f5db3e5282') self.assertEqual(self.items[1].mb_artistid, '80b3cf5e-18fe-4c59-98c7-e5bb87210710')
def mbsync_albums(lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for albums matched by query and their items. """ # Process matching albums. for a in lib.albums(query): if not a.mb_albumid: log.info(u'Skipping album {0}: has no mb_albumid'.format(a.id)) continue items = list(a.items()) # Get the MusicBrainz album information. album_info = hooks.album_for_mbid(a.mb_albumid) if not album_info: log.info(u'Release ID not found: {0}'.format(a.mb_albumid)) continue # Construct a track mapping according to MBIDs. This should work # for albums that have missing or extra tracks. mapping = {} for item in items: for track_info in album_info.tracks: if item.mb_trackid == track_info.track_id: mapping[item] = track_info break # Apply. with lib.transaction(): autotag.apply_metadata(album_info, mapping) changed = False for item in items: item_changed = ui.show_model_changes(item) changed |= item_changed if item_changed: apply_item_changes(lib, item, move, pretend, write) if not changed: # No change to any item. continue if not pretend: # Update album structure to reflect an item in it. for key in library.Album.item_keys: a[key] = items[0][key] a.store() # Move album art (and any inconsistent items). if move and lib.directory in util.ancestry(items[0].path): log.debug(u'moving album {0}'.format(a.id)) a.move()
def mbsync_albums(lib, query, move, pretend, write): """Synchronize matching albums. """ # Process matching albums. for a in lib.albums(query): if not a.mb_albumid: log.info(u'Skipping album {0}: has no mb_albumid'.format(a.id)) continue items = list(a.items()) for item in items: item.old_data = dict(item.record) # Get the MusicBrainz album information. matches = hooks._album_for_id(a.mb_albumid) if not matches: log.info(u'Release ID not found: {0}'.format(a.mb_albumid)) continue album_info = matches[0] # Construct a track mapping according to MBIDs. This should work # for albums that have missing or extra tracks. mapping = {} for item in items: for track_info in album_info.tracks: if item.mb_trackid == track_info.track_id: mapping[item] = track_info break # Apply. with lib.transaction(): autotag.apply_metadata(album_info, mapping) changed = False for item in items: changed = _print_and_apply_changes(lib, item, move, pretend, write) or changed if not changed: # No change to any item. continue if not pretend: # Update album structure to reflect an item in it. for key in library.ALBUM_KEYS_ITEM: setattr(a, key, getattr(items[0], key)) # Move album art (and any inconsistent items). if move and lib.directory in util.ancestry(items[0].path): log.debug(u'moving album {0}'.format(a.id)) a.move()
def show_changes(self, lib, task, match=None): if match is None: match = task.match changes = False if task.is_album: newmapping = {new_item(item): track_info for item, track_info in match.mapping.items()} autotag.apply_metadata(match.info, newmapping) # olditems[0].get_album() isn't working, create our own to compare olditems = list(match.mapping.keys()) oldvalues = dict((key, olditems[0][key]) for key in self.album_fields) oldalbum = library.Album(lib, **oldvalues) newitems = list(newmapping.keys()) values = dict((key, newitems[0][key]) for key in self.album_fields) album = library.Album(lib, **values) compare_fields = self.get_fields(self.album_fields, oldvalues) album_changes = show_model_changes(album, oldalbum, compare_fields) if album_changes: changes = True new_by_info = {track_info: item for item, track_info in newmapping.items()} for item, track_info in match.mapping.items(): newitem = new_by_info[track_info] compare_fields = self.get_fields(self.nonalbum_fields, item) item_changes = show_model_changes(newitem, item, compare_fields) if item_changes: changes = True else: fakeitem = new_item(task.item) autotag.apply_item_metadata(fakeitem, match.info) compare_fields = self.get_fields(self.all_fields, task.item) changes = show_model_changes(fakeitem, task.item, compare_fields) return changes
def apply_choices(config): """A coroutine for applying changes to albums during the autotag process. The parameters to the generator control the behavior of the import. The coroutine accepts ImportTask objects and yields nothing. """ lib = _reopen_lib(config.lib) while True: task = yield # Don't do anything if we're skipping the album or we're done. if task.sentinel or task.choice_flag == action.SKIP: if config.resume is not False: task.save_progress() continue # Change metadata, move, and copy. if task.should_write_tags(): if task.is_album: autotag.apply_metadata(task.items, task.info) else: autotag.apply_item_metadata(task.item, task.info) items = task.items if task.is_album else [task.item] if config.copy and config.delete: old_paths = [os.path.realpath(syspath(item.path)) for item in items] for item in items: if config.copy: item.move(lib, True, task.should_create_album()) if config.write and task.should_write_tags(): item.write() # Add items to library. We consolidate this at the end to avoid # locking while we do the copying and tag updates. if task.should_create_album(): # Add an album. albuminfo = lib.add_album(task.items, infer_aa = task.should_infer_aa()) else: # Add tracks. for item in items: lib.add(item) lib.save() # Get album art if requested. if config.art and task.should_fetch_art(): artpath = beets.autotag.art.art_for_album(task.info) if artpath: albuminfo.set_art(artpath) lib.save() # Announce that we've added an album. if task.should_create_album(): plugins.send('album_imported', lib=lib, album=albuminfo) else: plugins.send('item_imported', lib=lib, item=task.item) # Finally, delete old files. if config.copy and config.delete: new_paths = [os.path.realpath(item.path) for item in items] for old_path in old_paths: # Only delete files that were actually moved. if old_path not in new_paths: os.remove(syspath(old_path)) # Update progress. if config.resume is not False: task.save_progress()
def test_disc_total_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].disctotal, 2) self.assertEqual(self.items[1].disctotal, 2)
def test_albumtype_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].albumtype, "album") self.assertEqual(self.items[1].albumtype, "album")
def test_titles_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].title, "oneNew") self.assertEqual(self.items[1].title, "twoNew")
def test_track_index_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].track, 1) self.assertEqual(self.items[1].track, 2)
def apply_metadata(self): """Copy metadata from match info to the items. """ # TODO call should be more descriptive like # apply_metadata(self.match, self.items) autotag.apply_metadata(self.match.info, self.match.mapping)
def test_mb_trackid_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].mb_trackid, 'dfa939ec-118c-4d0f-84a0-60f3d1e6522c') self.assertEqual(self.items[1].mb_trackid, '40130ed1-a27c-42fd-a328-1ebefb6caef4')
def test_album_and_track_artists_separate(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].artist, 'artistOneNew') self.assertEqual(self.items[1].artist, 'artistTwoNew') self.assertEqual(self.items[0].albumartist, 'variousNew') self.assertEqual(self.items[1].albumartist, 'variousNew')
def test_disc_index_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].disc, 1) self.assertEqual(self.items[1].disc, 2)
def test_artist_sort_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].albumartist_sort, 'albumArtistSort') self.assertEqual(self.items[0].artist_sort, 'trackArtistSort') self.assertEqual(self.items[1].albumartist_sort, 'albumArtistSort') self.assertEqual(self.items[1].artist_sort, 'albumArtistSort')
def test_album_artist_overrides_empty_track_artist(self): my_info = copy.deepcopy(self.info) autotag.apply_metadata(self.items, my_info) self.assertEqual(self.items[0].artist, 'artistNew') self.assertEqual(self.items[0].artist, 'artistNew')
def test_albumtype_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].albumtype, 'album') self.assertEqual(self.items[1].albumtype, 'album')
def test_va_flag_cleared_does_not_set_comp(self): autotag.apply_metadata(self.items, self.info) self.assertFalse(self.items[0].comp) self.assertFalse(self.items[1].comp)
def apply_metadata(self): """Copy metadata from match info to the items. """ autotag.apply_metadata(self.match.info, self.match.mapping)
def test_va_flag_sets_comp(self): va_info = copy.deepcopy(self.info) va_info.va = True autotag.apply_metadata(self.items, va_info) self.assertTrue(self.items[0].comp) self.assertTrue(self.items[1].comp)
def test_va_flag_sets_comp(self): va_info = dict(self.info) # make a copy va_info['va'] = True autotag.apply_metadata(self.items, va_info) self.assertTrue(self.items[0].comp) self.assertTrue(self.items[1].comp)
def test_album_and_artist_applied_to_all(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].album, 'albumNew') self.assertEqual(self.items[1].album, 'albumNew') self.assertEqual(self.items[0].artist, 'artistNew') self.assertEqual(self.items[1].artist, 'artistNew')
def test_titles_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].title, 'oneNew') self.assertEqual(self.items[1].title, 'twoNew')
def test_album_and_artist_applied_to_all(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].album, "albumNew") self.assertEqual(self.items[1].album, "albumNew") self.assertEqual(self.items[0].artist, "artistNew") self.assertEqual(self.items[1].artist, "artistNew")
def apply_choices(config): """A coroutine for applying changes to albums during the autotag process. """ lib = _reopen_lib(config.lib) task = None while True: task = yield task if task.should_skip(): continue items = [i for i in task.items if i] if task.is_album else [task.item] # Clear IDs in case the items are being re-tagged. for item in items: item.id = None item.album_id = None # Change metadata. if task.should_write_tags(): if task.is_album: autotag.apply_metadata(task.items, task.info) else: autotag.apply_item_metadata(task.item, task.info) # Infer album-level fields. if task.is_album: _infer_album_fields(task) # Find existing item entries that these are replacing. Old # album structures are automatically cleaned up when the # last item is removed. replaced_items = defaultdict(list) for item in items: dup_items = lib.items(library.MatchQuery('path', item.path)) for dup_item in dup_items: replaced_items[item].append(dup_item) log.debug('replacing item %i: %s' % (dup_item.id, displayable_path(item.path))) log.debug('%i of %i items replaced' % (len(replaced_items), len(items))) # Move/copy files. task.old_paths = [item.path for item in items] for item in items: if config.copy: # If we're replacing an item, then move rather than # copying. do_copy = not bool(replaced_items[item]) lib.move(item, do_copy, task.is_album) if config.write and task.should_write_tags(): item.write() # Add items to library. We consolidate this at the end to avoid # locking while we do the copying and tag updates. try: # Remove old items. for replaced in replaced_items.itervalues(): for item in replaced: lib.remove(item) # Add new ones. if task.is_album: # Add an album. album = lib.add_album(items) task.album_id = album.id else: # Add tracks. for item in items: lib.add(item) finally: lib.save()
def albums(self, lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for albums matched by query and their items. """ # Process matching albums. for a in lib.albums(query): album_formatted = format(a) if not a.mb_albumid: self._log.info(u'Skipping album with no mb_albumid: {0}', album_formatted) continue items = list(a.items()) # Get the MusicBrainz album information. album_info = hooks.album_for_mbid(a.mb_albumid) if not album_info: self._log.info(u'Release ID {0} not found for album {1}', a.mb_albumid, album_formatted) continue # Map release track and recording MBIDs to their information. # Recordings can appear multiple times on a release, so each MBID # maps to a list of TrackInfo objects. releasetrack_index = dict() track_index = defaultdict(list) for track_info in album_info.tracks: releasetrack_index[track_info.release_track_id] = track_info track_index[track_info.track_id].append(track_info) # Construct a track mapping according to MBIDs (release track MBIDs # first, if available, and recording MBIDs otherwise). This should # work for albums that have missing or extra tracks. mapping = {} for item in items: if item.mb_releasetrackid and \ item.mb_releasetrackid in releasetrack_index: mapping[item] = releasetrack_index[item.mb_releasetrackid] else: candidates = track_index[item.mb_trackid] if len(candidates) == 1: mapping[item] = candidates[0] else: # If there are multiple copies of a recording, they are # disambiguated using their disc and track number. for c in candidates: if (c.medium_index == item.track and c.medium == item.disc): mapping[item] = c break # Apply. self._log.debug(u'applying changes to {}', album_formatted) with lib.transaction(): autotag.apply_metadata(album_info, mapping) changed = False # Find any changed item to apply MusicBrainz changes to album. any_changed_item = items[0] for item in items: item_changed = ui.show_model_changes(item) changed |= item_changed if item_changed: any_changed_item = item apply_item_changes(lib, item, move, pretend, write) if not changed: # No change to any item. continue if not pretend: # Update album structure to reflect an item in it. for key in library.Album.item_keys: a[key] = any_changed_item[key] a.store() # Move album art (and any inconsistent items). if move and lib.directory in util.ancestry(items[0].path): self._log.debug(u'moving album {0}', album_formatted) a.move()
def test_mb_trackid_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].mb_trackid, "dfa939ec-118c-4d0f-84a0-60f3d1e6522c") self.assertEqual(self.items[1].mb_trackid, "40130ed1-a27c-42fd-a328-1ebefb6caef4")
def apply_choices(config): """A coroutine for applying changes to albums during the autotag process. """ lib = _reopen_lib(config.lib) task = None while True: task = yield task if task.should_skip(): continue items = task.all_items() # Clear IDs in case the items are being re-tagged. for item in items: item.id = None item.album_id = None # Change metadata. if task.should_write_tags(): if task.is_album: autotag.apply_metadata(task.items, task.info) else: autotag.apply_item_metadata(task.item, task.info) plugins.send('import_task_apply', config=config, task=task) # Infer album-level fields. if task.is_album: _infer_album_fields(task) # Find existing item entries that these are replacing (for # re-imports). Old album structures are automatically cleaned up # when the last item is removed. replaced_items = defaultdict(list) for item in items: dup_items = lib.items(library.MatchQuery('path', item.path)) for dup_item in dup_items: replaced_items[item].append(dup_item) log.debug('replacing item %i: %s' % (dup_item.id, displayable_path(item.path))) log.debug('%i of %i items replaced' % (len(replaced_items), len(items))) # Find old items that should be replaced as part of a duplicate # resolution. duplicate_items = [] if task.remove_duplicates: if task.is_album: for album in _duplicate_check(lib, task): duplicate_items += album.items() else: duplicate_items = _item_duplicate_check(lib, task) log.debug('removing %i old duplicated items' % len(duplicate_items)) # Delete duplicate files that are located inside the library # directory. for duplicate_path in [i.path for i in duplicate_items]: if lib.directory in util.ancestry(duplicate_path): log.debug(u'deleting replaced duplicate %s' % util.displayable_path(duplicate_path)) util.soft_remove(duplicate_path) util.prune_dirs(os.path.dirname(duplicate_path), lib.directory) # Add items -- before path changes -- to the library. We add the # items now (rather than at the end) so that album structures # are in place before calls to destination(). try: # Remove old items. for replaced in replaced_items.itervalues(): for item in replaced: lib.remove(item) for item in duplicate_items: lib.remove(item) # Add new ones. if task.is_album: # Add an album. album = lib.add_album(items) task.album_id = album.id else: # Add tracks. for item in items: lib.add(item) finally: lib.save() # Move/copy files. task.old_paths = [item.path for item in items] # For deletion. for item in items: if config.copy or config.move: if config.move: # Just move the file. lib.move(item, False) else: # If it's a reimport, move the file. Otherwise, copy # and keep track of the old path. old_path = item.path do_copy = not bool(replaced_items[item]) lib.move(item, do_copy) if not do_copy: # If we moved the item, remove the now-nonexistent # file from old_paths. task.old_paths.remove(old_path) if config.write and task.should_write_tags(): item.write() # Save new paths. try: for item in items: lib.store(item) finally: lib.save()
def test_album_artist_overrides_empty_track_artist(self): my_info = copy.deepcopy(self.info) autotag.apply_metadata(self.items, my_info) self.assertEqual(self.items[0].artist, "artistNew") self.assertEqual(self.items[0].artist, "artistNew")
def write_metadata_changes(self): assert self.is_completed, "Processing is not finished for task {}".format(self.id) apply_metadata(self._album_info_object, self._track_mapping_object) for item in self: item.write()
def test_album_and_track_artists_separate(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].artist, "artistOneNew") self.assertEqual(self.items[1].artist, "artistTwoNew") self.assertEqual(self.items[0].albumartist, "variousNew") self.assertEqual(self.items[1].albumartist, "variousNew")
return if str(rec) == 'recommendation.none': logger.warn('No accurate album match found for %s, %s - not writing metadata', release['ArtistName'], release['AlbumTitle']) return if candidates: dist, info, mapping, extra_items, extra_tracks = candidates[0] else: logger.warn('No accurate album match found for %s, %s - not writing metadata', release['ArtistName'], release['AlbumTitle']) return logger.info('Beets recommendation for tagging items: %s' % rec) # TODO: Handle extra_items & extra_tracks autotag.apply_metadata(info, mapping) for item in items: try: item.write() logger.info("Successfully applied metadata to: %s", item.path.decode(headphones.SYS_ENCODING, 'replace')) except Exception, e: logger.warn("Error writing metadata to '%s': %s", item.path.decode(headphones.SYS_ENCODING, 'replace'), str(e)) def embedLyrics(downloaded_track_list): logger.info('Adding lyrics') # TODO: If adding lyrics for flac & lossy, only fetch the lyrics once # and apply it to both files for downloaded_track in downloaded_track_list:
def apply_choices(config): """A coroutine for applying changes to albums during the autotag process. """ lib = _reopen_lib(config.lib) task = None while True: task = yield task if task.should_skip(): continue items = [i for i in task.items if i] if task.is_album else [task.item] # Clear IDs in case the items are being re-tagged. for item in items: item.id = None item.album_id = None # Change metadata. if task.should_write_tags(): if task.is_album: autotag.apply_metadata(task.items, task.info) else: autotag.apply_item_metadata(task.item, task.info) # Infer album-level fields. if task.is_album: _infer_album_fields(task) # Find existing item entries that these are replacing. Old # album structures are automatically cleaned up when the # last item is removed. replaced_items = defaultdict(list) for item in items: dup_items = lib.items(library.MatchQuery('path', item.path)) for dup_item in dup_items: replaced_items[item].append(dup_item) log.debug('replacing item %i: %s' % (dup_item.id, displayable_path(item.path))) log.debug('%i of %i items replaced' % (len(replaced_items), len(items))) # Move/copy files. task.old_paths = [item.path for item in items] for item in items: if config.copy: # If we're replacing an item, then move rather than # copying. old_path = item.path do_copy = not bool(replaced_items[item]) lib.move(item, do_copy, task.is_album) if not do_copy: # If we moved the item, remove the now-nonexistent # file from old_paths. task.old_paths.remove(old_path) if config.write and task.should_write_tags(): item.write() # Add items to library. We consolidate this at the end to avoid # locking while we do the copying and tag updates. try: # Remove old items. for replaced in replaced_items.itervalues(): for item in replaced: lib.remove(item) # Add new ones. if task.is_album: # Add an album. album = lib.add_album(items) task.album_id = album.id else: # Add tracks. for item in items: lib.add(item) finally: lib.save()
def mbsync_albums(lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for albums matched by query and their items. """ # Process matching albums. for a in lib.albums(query): if not a.mb_albumid: log.info(u'Skipping album {0}: has no mb_albumid'.format(a.id)) continue items = list(a.items()) # Get the MusicBrainz album information. album_info = hooks.album_for_mbid(a.mb_albumid) if not album_info: log.info(u'Release ID not found: {0}'.format(a.mb_albumid)) continue # Map recording MBIDs to their information. Recordings can appear # multiple times on a release, so each MBID maps to a list of TrackInfo # objects. track_index = defaultdict(list) for track_info in album_info.tracks: track_index[track_info.track_id].append(track_info) # Construct a track mapping according to MBIDs. This should work # for albums that have missing or extra tracks. If there are multiple # copies of a recording, they are disambiguated using their disc and # track number. mapping = {} for item in items: candidates = track_index[item.mb_trackid] if len(candidates) == 1: mapping[item] = candidates[0] else: for c in candidates: if c.medium_index == item.track and c.medium == item.disc: mapping[item] = c break # Apply. with lib.transaction(): autotag.apply_metadata(album_info, mapping) changed = False for item in items: item_changed = ui.show_model_changes(item) changed |= item_changed if item_changed: apply_item_changes(lib, item, move, pretend, write) if not changed: # No change to any item. continue if not pretend: # Update album structure to reflect an item in it. for key in library.Album.item_keys: a[key] = items[0][key] a.store() # Move album art (and any inconsistent items). if move and lib.directory in util.ancestry(items[0].path): log.debug(u'moving album {0}'.format(a.id)) a.move()
def mbsync_albums(lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for albums matched by query and their items. """ # Process matching albums. for a in lib.albums(query): if not a.mb_albumid: log.info(u'Skipping album {0}: has no mb_albumid', a.id) continue items = list(a.items()) # Get the MusicBrainz album information. album_info = hooks.album_for_mbid(a.mb_albumid) if not album_info: log.info(u'Release ID not found: {0}', a.mb_albumid) continue # Map recording MBIDs to their information. Recordings can appear # multiple times on a release, so each MBID maps to a list of TrackInfo # objects. track_index = defaultdict(list) for track_info in album_info.tracks: track_index[track_info.track_id].append(track_info) # Construct a track mapping according to MBIDs. This should work # for albums that have missing or extra tracks. If there are multiple # copies of a recording, they are disambiguated using their disc and # track number. mapping = {} for item in items: candidates = track_index[item.mb_trackid] if len(candidates) == 1: mapping[item] = candidates[0] else: for c in candidates: if c.medium_index == item.track and c.medium == item.disc: mapping[item] = c break # Apply. with lib.transaction(): autotag.apply_metadata(album_info, mapping) changed = False for item in items: item_changed = ui.show_model_changes(item) changed |= item_changed if item_changed: apply_item_changes(lib, item, move, pretend, write) if not changed: # No change to any item. continue if not pretend: # Update album structure to reflect an item in it. for key in library.Album.item_keys: a[key] = items[0][key] a.store() # Move album art (and any inconsistent items). if move and lib.directory in util.ancestry(items[0].path): log.debug(u'moving album {0}', a.id) a.move()
def apply_choices(session): """A coroutine for applying changes to albums and singletons during the autotag process. """ task = None while True: task = yield task if task.should_skip(): continue items = task.imported_items() # Clear IDs in case the items are being re-tagged. for item in items: item.id = None item.album_id = None # Change metadata. if task.should_write_tags(): if task.is_album: autotag.apply_metadata( task.match.info, task.match.mapping ) else: autotag.apply_item_metadata(task.item, task.match.info) plugins.send('import_task_apply', session=session, task=task) # Infer album-level fields. if task.is_album: _infer_album_fields(task) # Find existing item entries that these are replacing (for # re-imports). Old album structures are automatically cleaned up # when the last item is removed. task.replaced_items = defaultdict(list) for item in items: dup_items = session.lib.items(library.MatchQuery('path', item.path)) for dup_item in dup_items: task.replaced_items[item].append(dup_item) log.debug('replacing item %i: %s' % (dup_item.id, displayable_path(item.path))) log.debug('%i of %i items replaced' % (len(task.replaced_items), len(items))) # Find old items that should be replaced as part of a duplicate # resolution. duplicate_items = [] if task.remove_duplicates: if task.is_album: for album in _duplicate_check(session.lib, task): duplicate_items += album.items() else: duplicate_items = _item_duplicate_check(session.lib, task) log.debug('removing %i old duplicated items' % len(duplicate_items)) # Delete duplicate files that are located inside the library # directory. for duplicate_path in [i.path for i in duplicate_items]: if session.lib.directory in util.ancestry(duplicate_path): log.debug(u'deleting replaced duplicate %s' % util.displayable_path(duplicate_path)) util.remove(duplicate_path) util.prune_dirs(os.path.dirname(duplicate_path), session.lib.directory) # Add items -- before path changes -- to the library. We add the # items now (rather than at the end) so that album structures # are in place before calls to destination(). with session.lib.transaction(): # Remove old items. for replaced in task.replaced_items.itervalues(): for item in replaced: session.lib.remove(item) for item in duplicate_items: session.lib.remove(item) # Add new ones. if task.is_album: # Add an album. album = session.lib.add_album(items) task.album_id = album.id else: # Add tracks. for item in items: session.lib.add(item)
def apply_choices(session): """A coroutine for applying changes to albums and singletons during the autotag process. """ task = None while True: task = yield task if task.should_skip(): continue items = task.imported_items() # Clear IDs in case the items are being re-tagged. for item in items: item.id = None item.album_id = None # Change metadata. if task.should_write_tags(): if task.is_album: autotag.apply_metadata(task.match.info, task.match.mapping) else: autotag.apply_item_metadata(task.item, task.match.info) plugins.send('import_task_apply', session=session, task=task) # Infer album-level fields. if task.is_album: _infer_album_fields(task) # Find existing item entries that these are replacing (for # re-imports). Old album structures are automatically cleaned up # when the last item is removed. task.replaced_items = defaultdict(list) for item in items: dup_items = session.lib.items(library.MatchQuery( 'path', item.path)) for dup_item in dup_items: task.replaced_items[item].append(dup_item) log.debug('replacing item %i: %s' % (dup_item.id, displayable_path(item.path))) log.debug('%i of %i items replaced' % (len(task.replaced_items), len(items))) # Find old items that should be replaced as part of a duplicate # resolution. duplicate_items = [] if task.remove_duplicates: if task.is_album: for album in _duplicate_check(session.lib, task): duplicate_items += album.items() else: duplicate_items = _item_duplicate_check(session.lib, task) log.debug('removing %i old duplicated items' % len(duplicate_items)) # Delete duplicate files that are located inside the library # directory. for duplicate_path in [i.path for i in duplicate_items]: if session.lib.directory in util.ancestry(duplicate_path): log.debug(u'deleting replaced duplicate %s' % util.displayable_path(duplicate_path)) util.remove(duplicate_path) util.prune_dirs(os.path.dirname(duplicate_path), session.lib.directory) # Add items -- before path changes -- to the library. We add the # items now (rather than at the end) so that album structures # are in place before calls to destination(). with session.lib.transaction(): # Remove old items. for replaced in task.replaced_items.itervalues(): for item in replaced: session.lib.remove(item) for item in duplicate_items: session.lib.remove(item) # Add new ones. if task.is_album: # Add an album. album = session.lib.add_album(items) task.album_id = album.id else: # Add tracks. for item in items: session.lib.add(item)
def test_per_disc_numbering(self): autotag.apply_metadata(self.items, self.info, per_disc_numbering=True) self.assertEqual(self.items[0].track, 1) self.assertEqual(self.items[1].track, 1)