def _process_item(self, item, copy=False, move=False, delete=False, tag=False, fmt=''): """Process Item `item`. """ print_(format(item, fmt)) if copy: item.move(basedir=copy, operation=MoveOperation.COPY) item.store() if move: item.move(basedir=move) item.store() if delete: item.remove(delete=True) if tag: try: k, v = tag.split('=') except Exception: raise UserError(f"{PLUGIN}: can't parse k=v tag: {tag}") setattr(item, k, v) item.store()
def similar(lib, src_item, threshold=0.15, fmt='${difference}: ${path}'): for item in lib.items(): if item.path != src_item.path: d = diff(item, src_item) if d < threshold: s = fmt.replace('${difference}', '{:2.2f}'.format(d)) ui.print_(format(item, s))
def _miss(lib, opts, args): self.config.set_args(opts) fmt = self.config['format'].get() count = self.config['count'].get() total = self.config['total'].get() albums = lib.albums(decargs(args)) if total: print(sum([_missing_count(a) for a in albums])) return # Default format string for count mode. if count and not fmt: fmt = '$albumartist - $album: $missing' print fmt for album in albums: if count: if _missing_count(album): print_(format(album, fmt)) else: for (is_found, item) in _missing(album): if is_found: sys.stdout.write('+ ') else: sys.stdout.write('- ') print_obj(item, lib)
def show_stats(lib, query): """Shows some statistics about the matched items.""" items = lib.items(query) total_size = 0 total_time = 0.0 total_items = 0 artists = set() albums = set() for item in items: #fixme This is approximate, so people might complain that # this total size doesn't match "du -sh". Could fix this # by putting total file size in the database. total_size += int(item.length * item.bitrate / 8) total_time += item.length total_items += 1 artists.add(item.artist) albums.add(item.album) print_("""Tracks: %i Total time: %s Total size: %s Artists: %i Albums: %i""" % ( total_items, ui.human_seconds(total_time), ui.human_bytes(total_size), len(artists), len(albums) ))
def _process_item(self, item, copy=False, move=False, delete=False, tag=False, fmt=u''): """Process Item `item`. """ print_(format(item, fmt)) if copy: item.move(basedir=copy, copy=True) item.store() if move: item.move(basedir=move, copy=False) item.store() if delete: item.remove(delete=True) if tag: try: k, v = tag.split('=') except Exception: raise UserError(u"{}: can't parse k=v tag: {}".format( PLUGIN, tag)) setattr(item, k, v) item.store()
def info(paths): # Set up fields to output. fields = [] for name, _, _, mffield in library.ITEM_FIELDS: if mffield: fields.append(name) # Line format. other_fields = ['album art'] maxwidth = max(len(name) for name in fields + other_fields) lineformat = u'{{0:>{0}}}: {{1}}'.format(maxwidth) first = True for path in paths: if not first: ui.print_() path = util.normpath(path) ui.print_(path) try: mf = mediafile.MediaFile(path) except mediafile.UnreadableFileError: ui.print_('cannot read file') continue # Basic fields. for name in fields: ui.print_(lineformat.format(name, getattr(mf, name))) # Extra stuff. ui.print_(lineformat.format('album art', mf.art is not None)) first = False
def choose_item(task, config): """Ask the user for a choice about tagging a single item. Returns either an action constant or a track info dictionary. """ print_() print_(task.item.path) candidates, rec = task.item_match if config.quiet: # Quiet mode; make a decision. if task.rec == autotag.RECOMMEND_STRONG: dist, track_info = candidates[0] show_item_change(task.item.color) return track_info else: return _quiet_fall_back(config) while True: # Ask for a choice. choice = choose_candidate(candidates, True, rec, config.color, config.interactive_autotag, item=task.item) if choice in (importer.action.SKIP, importer.action.ASIS): return choice elif choice == importer.action.TRACKS: assert False # TRACKS is only legal for albums. elif choice == importer.action.MANUAL: # Continue in the loop with a new set of candidates. search_artist, search_title = manual_search(False) candidates, rec = autotag.tag_item(task.item, search_artist, search_title) else: # Chose a candidate. assert not isinstance(choice, importer.action) return choice
def album_func(self, albums, opts): tagged_item_count = 0 tagged_album_count = 0 for album in albums: self.albumartist = None self.should_set = opts.reset if not opts.reset or not opts.quiet: for item in album.items(): if 'vt_albumartist' in item: if self.albumartist is None: self.albumartist = item['vt_albumartist'] elif self.albumartist != item['vt_albumartist']: self.albumartist = "" else: self.should_set = True else: self.albumartist = album.albumartist if not self.should_set: continue if not opts.quiet: fmt = u' $albumartist - $album' self.process_item(album, fmt) for item in album.items(): tagged_item_count += self.try_sync(item) tagged_album_count += 1 ui.print_() ui.print_(u'Changed {} item(s) in {} album(s)'.format(tagged_item_count, tagged_album_count))
def upload(self, lib, opts, args): items = lib.items(ui.decargs(args)) files = self.getpaths(items) self.authenticate() ui.print_(u'Uploading your files...') self.m.upload(filepaths=files) ui.print_(u'Your files were successfully added to library')
def update(self, create=None): if not os.path.isdir(self.directory) and not self.ask_create(create): print_(u'Skipping creation of {0}' .format(displayable_path(self.directory))) return converter = self.converter() for (item, action) in self.items_action(): dest = self.destination(item) path = self.get_path(item) if action == self.MOVE: print_(u'>{0} -> {1}'.format(displayable_path(path), displayable_path(dest))) util.mkdirall(dest) util.move(path, dest) util.prune_dirs(os.path.dirname(path), root=self.directory) self.set_path(item, dest) item.store() item.write(path=dest) elif action == self.WRITE: print_(u'*{0}'.format(displayable_path(path))) item.write(path=path) elif action == self.ADD: print_(u'+{0}'.format(displayable_path(dest))) converter.submit(item) elif action == self.REMOVE: print_(u'-{0}'.format(displayable_path(path))) self.remove_item(item) item.store() for item, dest in converter.as_completed(): self.set_path(item, dest) item.store() converter.shutdown()
def album_imported(self, lib, album): if self.on_import == False : pass print_("Tagging Lyrics: %s - %s" % (album.albumartist, album.album)) def fetch(item, artist, title): try: #print_(" -%s:" % (title), ui.colorize('yellow', 'Fetching')) lyrics = self.fetchLyrics(scrub(artist), scrub(title)) return (item, lyrics) except: return None def tag( item, lyrics): try: #print_(" -%s:" % (item.title), ui.colorize('green', 'Updated!')) item.lyrics = lyrics item.write() lib.store(item) except: pass [(item, item.artist, item.title) for item in album.items()] \ >> ThreadPool(apply(fetch), poolsize=self.processcount) \ >> filter( lambda itm: itm != None) \ >> ThreadPool(apply(tag), poolsize=1)
def _summary_judment(rec): """Determines whether a decision should be made without even asking the user. This occurs in quiet mode and when an action is chosen for NONE recommendations. Return an action or None if the user should be queried. May also print to the console if a summary judgment is made. """ if config['import']['quiet']: if rec == recommendation.strong: return importer.action.APPLY else: action = config['import']['quiet_fallback'].as_choice({ 'skip': importer.action.SKIP, 'asis': importer.action.ASIS, }) elif rec == recommendation.none: action = config['import']['none_rec_action'].as_choice({ 'skip': importer.action.SKIP, 'asis': importer.action.ASIS, 'ask': None, }) else: return None if action == importer.action.SKIP: print_('Skipping.') elif action == importer.action.ASIS: print_('Importing as-is.') return action
def command(self, lib, opts, args): self.config.set_args(opts) self.set_fields() query = decargs(args) for album in lib.albums(query): inconsistent_fields = defaultdict(list) album_items = album.items() for item in album_items: for field in self.included_fields: if item[field] != album[field]: inconsistent_fields[field].append(item) for field in sorted(inconsistent_fields): items = inconsistent_fields[field] if len(items) == len(album_items): print_( u'{}: field `{}` has album value `{}` but all track values are `{}`' .format(album, field, album[field], items[0][field])) else: for item in items: print_( u'{}: field `{}` has value `{}` but album value is `{}`' .format(item, field, item[field], album[field]))
def remove_items(lib, query, album, delete=False): """Remove items matching query from lib. If album, then match and remove whole albums. If delete, also remove files from disk. """ # Get the matching items. items, albums = _do_query(lib, query, album) # Show all the items. for item in items: print_(item.artist + ' - ' + item.album + ' - ' + item.title) # Confirm with user. print_() if delete: prompt = 'Really DELETE %i files (y/n)?' % len(items) else: prompt = 'Really remove %i items from the library (y/n)?' % \ len(items) if not ui.input_yn(prompt, True): return # Remove (and possibly delete) items. with lib.transaction(): if album: for al in albums: al.remove(delete) else: for item in items: lib.remove(item, delete)
def upload(self, lib, opts, args): items = lib.items(ui.decargs(args)) files = self.getpaths(items) self.authenticate() ui.print_('Uploading your files...') self.m.upload(filepaths=files) ui.print_('Your files were successfully added to library')
def search(self, lib, opts, args): password = config['gmusic']['password'] email = config['gmusic']['email'] password.redact = True email.redact = True # Since Musicmanager doesn't support library management # we need to use mobileclient interface mobile = Mobileclient() try: mobile.login(email.as_str(), password.as_str(), Mobileclient.FROM_MAC_ADDRESS) files = mobile.get_all_songs() except NotLoggedIn: ui.print_( u'Authentication error. Please check your email and password.' ) return if not args: for i, file in enumerate(files, start=1): print(i, ui.colorize('blue', file['artist']), file['title'], ui.colorize('red', file['album'])) else: if opts.track: self.match(files, args, 'title') else: self.match(files, args, 'artist')
def preview_playlist_command(self, lib, opts, args): name_column_length = 60 count = 10 self._log.info(config.user_config_path()) if opts.count: count = int(opts.count) if opts.playlist: if opts.playlist not in _settings.playlists: self._log.error(u'Playlist not defined: {}'.format(opts.playlist)) return query = _settings.playlists[opts.playlist].query.split(u" ") else: query = decargs(args) query = u" ".join(query) query = ignore_deleted_in_query(query) items = playlist_generator.generate_playlist(lib, _settings.rules, count, opts.shuffle, query) for item in items: score_string = ", ".join(['%s: %s' % (key.replace("rule_", ""), value) for (key, value) in sorted(item.scores.items())]) score_sum = round(sum(item.scores.values()), 2) item_name = unicode(item) if len(item_name) > name_column_length-5: item_name = item_name[:name_column_length-5] + "..." item_string = item_name.ljust(name_column_length) print_(u"Track: {0} Scores: {1}=[{2}]".format(item_string, score_sum, score_string))
def convert_func(self, lib, opts, args): dest = opts.dest or self.config['dest'].get() if not dest: raise ui.UserError(u'no convert destination set') dest = util.bytestring_path(dest) threads = opts.threads or self.config['threads'].get(int) path_formats = ui.get_path_formats(self.config['paths'] or None) fmt = opts.format or self.config['format'].as_str().lower() if opts.pretend is not None: pretend = opts.pretend else: pretend = self.config['pretend'].get(bool) if opts.hardlink is not None: hardlink = opts.hardlink link = False elif opts.link is not None: hardlink = False link = opts.link else: hardlink = self.config['hardlink'].get(bool) link = self.config['link'].get(bool) if opts.album: albums = lib.albums(ui.decargs(args)) items = [i for a in albums for i in a.items()] if not pretend: for a in albums: ui.print_(format(a, u'')) else: items = list(lib.items(ui.decargs(args))) if not pretend: for i in items: ui.print_(format(i, u'')) if not items: self._log.error(u'Empty query result.') return if not (pretend or opts.yes or ui.input_yn(u"Convert? (Y/n)")): return if opts.album and self.config['copy_album_art']: for album in albums: self.copy_album_art(album, dest, path_formats, pretend, link, hardlink) convert = [self.convert_item(dest, opts.keep_new, path_formats, fmt, pretend, link, hardlink) for _ in range(threads)] pipe = util.pipeline.Pipeline([iter(items), convert]) pipe.run_parallel()
def _process_item(item, lib, copy=False, move=False, delete=False, tag=False, format=''): """Process Item `item` in `lib`. """ if copy: item.move(basedir=copy, copy=True) item.store() if move: item.move(basedir=move, copy=False) item.store() if delete: item.remove(delete=True) if tag: try: k, v = tag.split('=') except: raise UserError('%s: can\'t parse k=v tag: %s' % (PLUGIN, tag)) setattr(k, v) item.store() print_(format(item, format))
def update(self, create=None): if not os.path.isdir(self.directory) and not self.ask_create(create): print_(u'Skipping creation of {0}'.format( displayable_path(self.directory))) return converter = self.converter() for (item, action) in self.items_action(): dest = self.destination(item) path = self.get_path(item) if action == self.MOVE: print_(u'>{0} -> {1}'.format(displayable_path(path), displayable_path(dest))) util.mkdirall(dest) util.move(path, dest) util.prune_dirs(os.path.dirname(path), root=self.directory) self.set_path(item, dest) item.store() item.write(path=dest) elif action == self.WRITE: print_(u'*{0}'.format(displayable_path(path))) item.write(path=path) elif action == self.ADD: print_(u'+{0}'.format(displayable_path(dest))) converter.submit(item) elif action == self.REMOVE: print_(u'-{0}'.format(displayable_path(path))) self.remove_item(item) item.store() for item, dest in converter.as_completed(): self.set_path(item, dest) item.store() converter.shutdown()
def remove_items(lib, query, album, delete): """Remove items matching query from lib. If album, then match and remove whole albums. If delete, also remove files from disk. """ # Get the matching items. items, albums = _do_query(lib, query, album) # Show all the items. for item in items: ui.print_obj(item, lib) # Confirm with user. print_() if delete: prompt = 'Really DELETE %i files (y/n)?' % len(items) else: prompt = 'Really remove %i items from the library (y/n)?' % \ len(items) if not ui.input_yn(prompt, True): return # Remove (and possibly delete) items. with lib.transaction(): for obj in (albums if album else items): obj.remove(delete)
def remove_items(lib, query, album, delete=False): """Remove items matching query from lib. If album, then match and remove whole albums. If delete, also remove files from disk. """ # Get the matching items. items, albums = _do_query(lib, query, album) # Show all the items. for item in items: print_(item.artist + ' - ' + item.album + ' - ' + item.title) # Confirm with user. print_() if delete: prompt = 'Really DELETE %i files (y/n)?' % len(items) else: prompt = 'Really remove %i items from the library (y/n)?' % \ len(items) if not ui.input_yn(prompt, True): return # Remove (and possibly delete) items. if album: for al in albums: al.remove(delete) else: for item in items: lib.remove(item, delete) lib.save()
def update_playlists(lib): ui.print_("Updating smart playlists...") playlists = config['smartplaylist']['playlists'].get(list) playlist_dir = config['smartplaylist']['playlist_dir'].as_filename() relative_to = config['smartplaylist']['relative_to'].get() if relative_to: relative_to = normpath(relative_to) for playlist in playlists: items = [] items.extend(_items_for_query(lib, playlist, True)) items.extend(_items_for_query(lib, playlist, False)) m3us = {} basename = playlist['name'].encode('utf8') # As we allow tags in the m3u names, we'll need to iterate through # the items and generate the correct m3u file names. for item in items: m3u_name = item.evaluate_template(basename, True) if not (m3u_name in m3us): m3us[m3u_name] = [] item_path = item.path if relative_to: item_path = os.path.relpath(item.path, relative_to) if item_path not in m3us[m3u_name]: m3us[m3u_name].append(item_path) # Now iterate through the m3us that we need to generate for m3u in m3us: m3u_path = normpath(os.path.join(playlist_dir, m3u)) with open(syspath(m3u_path), 'w') as f: for path in m3us[m3u]: f.write(path + '\n') ui.print_("... Done")
def show_stats(lib, query): """Shows some statistics about the matched items.""" items = lib.items(query) total_size = 0 total_time = 0.0 total_items = 0 artists = set() albums = set() for item in items: #fixme This is approximate, so people might complain that # this total size doesn't match "du -sh". Could fix this # by putting total file size in the database. total_size += int(item.length * item.bitrate / 8) total_time += item.length total_items += 1 artists.add(item.artist) albums.add(item.album) print_("""Tracks: %i Total time: %s Total size: %s Artists: %i Albums: %i""" % (total_items, ui.human_seconds(total_time), ui.human_bytes(total_size), len(artists), len(albums)))
def _missing_tracks(self, lib, query): """Print a listing of tracks missing from each album in the library matching query. """ albums = lib.albums(query) count = self.config['count'].get() total = self.config['total'].get() fmt = config['format_album' if count else 'format_item'].get() if total: print(sum([_missing_count(a) for a in albums])) return # Default format string for count mode. if count: fmt += ': $missing' for album in albums: if count: if _missing_count(album): print_(format(album, fmt)) else: for item in self._missing(album): print_(format(item, fmt))
def modify_items(lib, mods, query, write, move, album, confirm): """Modifies matching items according to key=value assignments.""" # Parse key=value specifications into a dictionary. if album: allowed_keys = library.ALBUM_KEYS else: allowed_keys = library.ITEM_KEYS_WRITABLE + ['added'] fsets = {} for mod in mods: key, value = mod.split('=', 1) if key not in allowed_keys: raise ui.UserError('"%s" is not a valid field' % key) fsets[key] = _convert_type(key, value, album) # Get the items to modify. items, albums = _do_query(lib, query, album, False) objs = albums if album else items # Preview change. print_('Modifying %i %ss.' % (len(objs), 'album' if album else 'item')) for obj in objs: # Identify the changed object. ui.print_obj(obj, lib) # Show each change. for field, value in fsets.iteritems(): curval = getattr(obj, field) _showdiff(field, curval, value) # Confirm. if confirm: extra = ' and write tags' if write else '' if not ui.input_yn('Really modify%s (Y/n)?' % extra): return # Apply changes to database. with lib.transaction(): for obj in objs: for field, value in fsets.iteritems(): setattr(obj, field, value) if move: cur_path = obj.item_dir() if album else obj.path if lib.directory in ancestry(cur_path): # In library? log.debug('moving object %s' % cur_path) if album: obj.move() else: lib.move(obj) # When modifying items, we have to store them to the database. if not album: lib.store(obj) # Apply tags if requested. if write: if album: items = itertools.chain(*(a.items() for a in albums)) for item in items: item.write()
def show_stats(lib, query, exact): """Shows some statistics about the matched items.""" items = lib.items(query) total_size = 0 total_time = 0.0 total_items = 0 artists = set() albums = set() for item in items: if exact: total_size += os.path.getsize(item.path) else: total_size += int(item.length * item.bitrate / 8) total_time += item.length total_items += 1 artists.add(item.artist) albums.add(item.album) size_str = '' + ui.human_bytes(total_size) if exact: size_str += ' ({0} bytes)'.format(total_size) print_("""Tracks: {0} Total time: {1} ({2:.2f} seconds) Total size: {3} Artists: {4} Albums: {5}""".format(total_items, ui.human_seconds(total_time), total_time, size_str, len(artists), len(albums)))
def search(self, lib, opts, args): password = config['gmusic']['password'] email = config['gmusic']['email'] password.redact = True email.redact = True # Since Musicmanager doesn't support library management # we need to use mobileclient interface mobile = Mobileclient() try: mobile.login(email.as_str(), password.as_str(), Mobileclient.FROM_MAC_ADDRESS) files = mobile.get_all_songs() except NotLoggedIn: ui.print_( u'Authentication error. Please check your email and password.') return if not args: for i, file in enumerate(files, start=1): print(i, ui.colorize('blue', file['artist']), file['title'], ui.colorize('red', file['album'])) else: if opts.track: self.match(files, args, 'title') else: self.match(files, args, 'artist')
def open(self, lib, opts, args): query = ui.decargs(args) if opts.album: items = lib.albums(query) else: items = lib.items(query) if not items: raise ui.UserError("nothing to open") cmd = util.open_anything() if opts.args: cmd += " " + opts.args paths = [item.path for item in items] if opts.reveal: paths = [os.path.dirname(p) for p in paths] self._log.debug("invoking command: {} {}", cmd, subprocess.list2cmdline(paths)) item_type = "album" if opts.album else "track" item_type += "s" if len(items) > 1 else "" if opts.reveal: action = "Revealing" else: action = "Opening" ui.print_("{} {} {}.".format(action, len(items), item_type)) try: util.interactive_open(paths, cmd) except OSError as exc: raise ui.UserError("failed to invoke {}: {}".format(cmd, exc))
def info(paths): # Set up fields to output. fields = [] for name, _, _, mffield in library.ITEM_FIELDS: if mffield: fields.append(name) # Line format. other_fields = ['album art'] maxwidth = max(len(name) for name in fields + other_fields) lineformat = u'{{:>{0}}}: {{0}}'.format(maxwidth) first = True for path in paths: if not first: ui.print_() path = util.normpath(path) ui.print_(path) try: mf = mediafile.MediaFile(path) except mediafile.UnreadableFileError: ui.print_('cannot read file') continue # Basic fields. for name in fields: ui.print_(lineformat.format(name, getattr(mf, name))) # Extra stuff. ui.print_(lineformat.format('album art', mf.art is not None)) first = False
def convert_func(self, lib, opts, args): (dest, threads, path_formats, fmt, pretend, hardlink, link) = self._get_opts_and_config(opts) if opts.album: albums = lib.albums(ui.decargs(args)) items = [i for a in albums for i in a.items()] if not pretend: for a in albums: ui.print_(format(a, '')) else: items = list(lib.items(ui.decargs(args))) if not pretend: for i in items: ui.print_(format(i, '')) if not items: self._log.error('Empty query result.') return if not (pretend or opts.yes or ui.input_yn("Convert? (Y/n)")): return if opts.album and self.config['copy_album_art']: for album in albums: self.copy_album_art(album, dest, path_formats, pretend, link, hardlink) self._parallel_convert(dest, opts.keep_new, path_formats, fmt, pretend, link, hardlink, threads, items)
def func(lib, opts, args): # The "write to files" option corresponds to the # import_write config value. write = ui.should_write() if opts.writerest: self.writerest_indexes(opts.writerest) for item in lib.items(ui.decargs(args)): if not opts.local_only and not self.config['local']: self.fetch_item_lyrics( lib, item, write, opts.force_refetch or self.config['force'], ) if item.lyrics: if opts.printlyr: ui.print_(item.lyrics) if opts.writerest: self.writerest(opts.writerest, item) if opts.writerest: # flush last artist self.writerest(opts.writerest, None) ui.print_(u'ReST files generated. to build, use one of:') ui.print_(u' sphinx-build -b html %s _build/html' % opts.writerest) ui.print_(u' sphinx-build -b epub %s _build/epub' % opts.writerest) ui.print_( (u' sphinx-build -b latex %s _build/latex ' u'&& make -C _build/latex all-pdf') % opts.writerest)
def _summary_judment(rec): """Determines whether a decision should be made without even asking the user. This occurs in quiet mode and when an action is chosen for NONE recommendations. Return an action or None if the user should be queried. May also print to the console if a summary judgment is made. """ if config["import"]["quiet"]: if rec == recommendation.strong: return importer.action.APPLY else: action = config["import"]["quiet_fallback"].as_choice( {"skip": importer.action.SKIP, "asis": importer.action.ASIS} ) elif rec == recommendation.none: action = config["import"]["none_rec_action"].as_choice( {"skip": importer.action.SKIP, "asis": importer.action.ASIS, "ask": None} ) else: return None if action == importer.action.SKIP: print_("Skipping.") elif action == importer.action.ASIS: print_("Importing as-is.") return action
def func(lib, opts, args): # The "write to files" option corresponds to the # import_write config value. write = ui.should_write() if opts.writerest: self.writerest_indexes(opts.writerest) for item in lib.items(ui.decargs(args)): if not opts.local_only and not self.config['local']: self.fetch_item_lyrics( lib, item, write, opts.force_refetch or self.config['force'], ) if item.lyrics: if opts.printlyr: ui.print_(item.lyrics) if opts.writerest: self.writerest(opts.writerest, item) if opts.writerest: # flush last artist self.writerest(opts.writerest, None) ui.print_(u'ReST files generated. to build, use one of:') ui.print_(u' sphinx-build -b html %s _build/html' % opts.writerest) ui.print_(u' sphinx-build -b epub %s _build/epub' % opts.writerest) ui.print_((u' sphinx-build -b latex %s _build/latex ' u'&& make -C _build/latex all-pdf') % opts.writerest)
def modify_items(lib, mods, query, write, move, album, confirm): """Modifies matching items according to key=value assignments.""" # Parse key=value specifications into a dictionary. model_cls = library.Album if album else library.Item fsets = {} for mod in mods: key, value = mod.split('=', 1) fsets[key] = model_cls._parse(key, value) # Get the items to modify. items, albums = _do_query(lib, query, album, False) objs = albums if album else items # Preview change and collect modified objects. print_('Modifying %i %ss.' % (len(objs), 'album' if album else 'item')) changed = set() for obj in objs: # Identify the changed object. ui.print_obj(obj, lib) # Show each change. for field, value in fsets.iteritems(): if _showdiff(field, obj._get_formatted(field), obj._format(field, value)): changed.add(obj) # Still something to do? if not changed: print_('No changes to make.') return # Confirm action. if confirm: extra = ' and write tags' if write else '' if not ui.input_yn('Really modify%s (Y/n)?' % extra): return # Apply changes to database. with lib.transaction(): for obj in changed: for field, value in fsets.iteritems(): obj[field] = value if move: cur_path = obj.path if lib.directory in ancestry(cur_path): # In library? log.debug('moving object %s' % cur_path) obj.move() obj.store() # Apply tags if requested. if write: if album: changed_items = itertools.chain(*(a.items() for a in changed)) else: changed_items = changed for item in changed_items: item.write()
def func(lib, opts, args): # The "write to files" option corresponds to the # import_write config value. write = ui.should_write() for item in lib.items(ui.decargs(args)): self.fetch_item_lyrics(lib, item, write, opts.force_refetch or self.config["force"]) if opts.printlyr and item.lyrics: ui.print_(item.lyrics)
def show_album(artist, album): if artist: album_description = u' %s - %s' % (artist, album) elif album: album_description = u' %s' % album else: album_description = u' (unknown album)' print_(album_description)
def func(lib, config, opts, args): # The "write to files" option corresponds to the # import_write config value. write = ui.config_val(config, "beets", "import_write", commands.DEFAULT_IMPORT_WRITE, bool) for item in lib.items(ui.decargs(args)): fetch_item_lyrics(lib, logging.INFO, item, write) if opts.printlyr and item.lyrics: ui.print_(item.lyrics)
def func(lib, opts, args): # The "write to files" option corresponds to the # import_write config value. write = config['import']['write'].get(bool) for item in lib.items(ui.decargs(args)): fetch_item_lyrics(lib, logging.INFO, item, write) if opts.printlyr and item.lyrics: ui.print_(item.lyrics)
def func(lib, opts, args): # The "write to files" option corresponds to the # import_write config value. write = config['import']['write'].get(bool) for item in lib.items(ui.decargs(args)): self.fetch_item_lyrics(lib, logging.INFO, item, write) if opts.printlyr and item.lyrics: ui.print_(item.lyrics)
def read_advisory(self, lib, opts, args): self.config.set_args(opts) query = decargs(args) items, _ = _do_query(lib, query, None, False) if not items: print_(u'No items matched the specified query: {0}.'.format(' '.join(query))) return self.read_items(lib, items, pretend=opts.pretend)
def list_command(self, lib, opts, args): self.handle_common_args(opts, args) for item, is_sole_track in self.list_sole_tracks(lib): if is_sole_track: ui.print_(format(item)) elif opts.non_matching and not is_sole_track: ui.print_(format(item))
def fetch(mf): try: print_(" -%s:" % (mf.title), ui.colorize('yellow', 'Fetching')) lyrics = self.fetchLyrics(scrub(mf.artist), scrub(mf.title)) result = (mf, lyrics); return result except: return None
def show_album(artist, album): if artist: album_description = u" %s - %s" % (artist, album) elif album: album_description = u" %s" % album else: album_description = u" (unknown album)" print_(album_description)
def modify_items(lib, mods, dels, query, write, move, album, confirm): """Modifies matching items according to key=value assignments.""" # Parse key=value specifications into a dictionary. model_cls = library.Album if album else library.Item fsets = {} for mod in mods: key, value = mod.split('=', 1) fsets[key] = model_cls._parse(key, value) # Get the items to modify. items, albums = _do_query(lib, query, album, False) objs = albums if album else items # Apply changes *temporarily*, preview them, and collect modified # objects. print_('Modifying %i %ss.' % (len(objs), 'album' if album else 'item')) changed = set() for obj in objs: for field, value in fsets.iteritems(): obj[field] = value for field in dels: del obj[field] if ui.show_model_changes(obj): changed.add(obj) # Still something to do? if not changed: print_('No changes to make.') return # Confirm action. if confirm: extra = ' and write tags' if write else '' if not ui.input_yn('Really modify%s (Y/n)?' % extra): return # Apply changes to database. with lib.transaction(): for obj in changed: if move: cur_path = obj.path if lib.directory in ancestry(cur_path): # In library? log.debug('moving object %s' % cur_path) obj.move() obj.store() # Apply tags if requested. if write: if album: changed_items = itertools.chain(*(a.items() for a in changed)) else: changed_items = changed for item in changed_items: try: item.write() except library.FileOperationError as exc: log.error(exc)
def update_items(lib, query, album, move, color): """For all the items matched by the query, update the library to reflect the item's embedded tags. """ items, _ = _do_query(lib, query, album) # Walk through the items and pick up their changes. affected_albums = set() for item in items: # Item deleted? if not os.path.exists(syspath(item.path)): print_(u'X %s - %s' % (item.artist, item.title)) lib.remove(item, True) affected_albums.add(item.album_id) continue # Read new data. old_data = dict(item.record) item.read() # Get and save metadata changes. changes = {} for key in library.ITEM_KEYS_META: if item.dirty[key]: changes[key] = old_data[key], getattr(item, key) if changes: # Something changed. print_(u'* %s - %s' % (item.artist, item.title)) for key, (oldval, newval) in changes.iteritems(): _showdiff(key, oldval, newval, color) # Move the item if it's in the library. if move and lib.directory in ancestry(item.path): item.move(lib) lib.store(item) affected_albums.add(item.album_id) # Modify affected albums to reflect changes in their items. for album_id in affected_albums: if album_id is None: # Singletons. continue album = lib.get_album(album_id) if not album: # Empty albums have already been removed. log.debug('emptied album %i' % album_id) continue al_items = list(album.items()) # Update album structure to reflect an item in it. for key in library.ALBUM_KEYS_ITEM: setattr(album, key, getattr(al_items[0], key)) # Move album art (and any inconsistent items). if move and lib.directory in ancestry(al_items[0].path): log.debug('moving album %i' % album_id) album.move() lib.save()
def choose_match(task, config): """Given an initial autotagging of items, go through an interactive dance with the user to ask for a choice of metadata. Returns an (info, items) pair, ASIS, or SKIP. """ # Show what we're tagging. print_() print_(task.path) if config.quiet: # No input; just make a decision. if task.rec == autotag.RECOMMEND_STRONG: dist, items, info = task.candidates[0] show_change(task.cur_artist, task.cur_album, items, info, dist, config.color) return info, items else: return _quiet_fall_back(config) # Loop until we have a choice. candidates, rec = task.candidates, task.rec while True: # Ask for a choice from the user. choice = choose_candidate( candidates, False, rec, config.color, config.timid, task.cur_artist, task.cur_album, itemcount=len(task.items), per_disc_numbering=config.per_disc_numbering, ) # Choose which tags to use. if choice in (importer.action.SKIP, importer.action.ASIS, importer.action.TRACKS): # Pass selection to main control flow. return choice elif choice is importer.action.MANUAL: # Try again with manual search terms. search_artist, search_album = manual_search(False) try: _, _, candidates, rec = autotag.tag_album(task.items, config.timid, search_artist, search_album) except autotag.AutotagError: candidates, rec = None, None elif choice is importer.action.MANUAL_ID: # Try a manually-entered ID. search_id = manual_id(False) if search_id: try: _, _, candidates, rec = autotag.tag_album(task.items, config.timid, search_id=search_id) except autotag.AutotagError: candidates, rec = None, None else: # We have a candidate! Finish tagging. Here, choice is # an (info, items) pair as desired. assert not isinstance(choice, importer.action) return choice
def func(lib, config, opts, args): # The "write to files" option corresponds to the # import_write config value. write = ui.config_val(config, 'beets', 'import_write', commands.DEFAULT_IMPORT_WRITE, bool) for item in lib.items(ui.decargs(args)): fetch_item_lyrics(lib, logging.INFO, item, write) if opts.printlyr and item.lyrics: ui.print_(item.lyrics)
def modify_items(self, lib, mods, dels, query, write, move, album, confirm): """Modifies matching items according to user-specified assignments and deletions. `mods` is a dictionary of field and value pairse indicating assignments. `dels` is a list of fields to be deleted. """ # Parse key=value specifications into a dictionary. model_cls = library.Album if album else library.Item # Get the items to modify. items, albums = _do_query(lib, query, album, False) objs = albums if album else items reconfirm = self.check_sanity(mods, dels, objs, album) if reconfirm: confirm = True # Apply changes *temporarily*, preview them, and collect modified # objects. print_(u'Modifying {0} {1}s.'.format(len(objs), u'album' if album else u'item')) changed = [] for obj in objs: obj_mods = {} for key, value in mods.items(): value = obj.evaluate_template(value) obj_mods[key] = model_cls._parse(key, value) if print_and_modify(obj, obj_mods, dels) and obj not in changed: changed.append(obj) # Still something to do? if not changed: print_(u'No changes to make.') return # Confirm action. if confirm: if write and move: extra = u', move and write tags' elif write: extra = u' and write tags' elif move: extra = u' and move' else: extra = u'' changed = ui.input_select_objects( u'Really modify%s' % extra, changed, lambda o: print_and_modify(o, mods, dels)) # Apply changes to database and files with lib.transaction(): for obj in changed: obj.try_sync(write, move)
def func(lib, opts, args): # The "write to files" option corresponds to the # import_write config value. write = config['import']['write'].get(bool) for item in lib.items(ui.decargs(args)): fetch_item_tempo(lib, logging.INFO, item, write) if opts.printbpm and item.bpm: ui.print_('{0} BPM'.format(item.bpm))