def _import_task_choice(self, task, session): match = task.match if not match: return match = match.info recording_id = match.track_id search_link = "https://musicbrainz.org/search?query=" + match.title.replace(' ', '+') \ + "+artist%3A%22" + match.artist.replace(' ', '+') \ + "%22&type=recording&limit=100&method=advanced" while not self._has_work_id(recording_id): recording_year = self._get_oldest_date(recording_id, _date_from_file(task.item.year, task.item.month, task.item.day)) recording_year_string = None if recording_year is None else str(recording_year['year']) self._log.error("{0.artist} - {0.title} ({1}) has no associated work! Please fix " "and try again!", match, recording_year_string) print("Search link: " + search_link) sel = ui.input_options(('Use this recording', 'Try again', 'Skip track')) if sel == "t": # Fetch data again self._fetch_recording(recording_id) elif sel == "u": return else: task.choice_flag = action.SKIP return
def importer_inspect_candidate(self, session, task): # Prompt the user for a candidate. sel = ui.input_options([], numrange=(1, len(task.candidates))) match = task.candidates[sel - 1] self.show_changes(session.lib, task, match) return None
def play_music(lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist. """ command = config['play']['command'].get() # If a command isn't set then let the OS decide how to open the playlist. if not command: sys_name = platform.system() if sys_name == 'Darwin': command = 'open' elif sys_name == 'Windows': command = 'start' else: # If not Mac or Win then assume Linux(or posix based). command = 'xdg-open' # Preform search by album and add folders rather then tracks to playlist. if opts.album: albums = lib.albums(ui.decargs(args)) paths = [] for album in albums: paths.append(album.item_dir()) item_type = 'album' # Preform item query and add tracks to playlist. else: paths = [item.path for item in lib.items(ui.decargs(args))] item_type = 'track' item_type += 's' if len(paths) > 1 else '' if not paths: ui.print_(ui.colorize('yellow', 'No {0} to play.'.format(item_type))) return # Warn user before playing any huge playlists. if len(paths) > 100: ui.print_( ui.colorize( 'yellow', 'You are about to queue {0} {1}.'.format( len(paths), item_type))) if ui.input_options(('Continue', 'Abort')) == 'a': return # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) for item in paths: m3u.write(item + '\n') m3u.close() # Invoke the command and log the output. output = util.command_output([command, m3u.name]) if output: log.debug(u'Output of {0}: {1}'.format(command, output)) ui.print_(u'Playing {0} {1}.'.format(len(paths), item_type))
def resolve_duplicate(self, task): """Decide what to do when a new album or item seems similar to one that's already in the library. """ log.warn("This %s is already in the library!" % ("album" if task.is_album else "item")) if config['import']['quiet']: # In quiet mode, don't prompt -- just skip. log.info('Skipping.') sel = 's' else: sel = ui.input_options(('Skip new', 'Keep both', 'Remove old')) if sel == 's': # Skip new. task.set_choice(importer.action.SKIP) elif sel == 'k': # Keep both. Do nothing; leave the choice intact. pass elif sel == 'r': # Remove old. task.remove_duplicates = True else: assert False
def resolve_duplicate(task, config): """Decide what to do when a new album or item seems similar to one that's already in the library. """ log.warn("This %s is already in the library!" % ("album" if task.is_album else "item")) if config.quiet: # In quiet mode, don't prompt -- just skip. log.info('Skipping.') sel = 's' else: sel = ui.input_options( ('Skip new', 'Keep both', 'Remove old'), color=config.color ) if sel == 's': # Skip new. task.set_choice(importer.action.SKIP) elif sel == 'k': # Keep both. Do nothing; leave the choice intact. pass elif sel == 'r': # Remove old. task.remove_duplicates = True else: assert False
def play_music(lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist. """ command = config['play']['command'].get() # If a command isn't set then let the OS decide how to open the playlist. if not command: sys_name = platform.system() if sys_name == 'Darwin': command = 'open' elif sys_name == 'Windows': command = 'start' else: # If not Mac or Win then assume Linux(or posix based). command = 'xdg-open' # Preform search by album and add folders rather then tracks to playlist. if opts.album: albums = lib.albums(ui.decargs(args)) paths = [] for album in albums: paths.append(album.item_dir()) item_type = 'album' # Preform item query and add tracks to playlist. else: paths = [item.path for item in lib.items(ui.decargs(args))] item_type = 'track' item_type += 's' if len(paths) > 1 else '' if not paths: ui.print_(ui.colorize('yellow', 'No {0} to play.'.format(item_type))) return # Warn user before playing any huge playlists. if len(paths) > 100: ui.print_(ui.colorize( 'yellow', 'You are about to queue {0} {1}.'.format(len(paths), item_type))) if ui.input_options(('Continue', 'Abort')) == 'a': return # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) for item in paths: m3u.write(item + '\n') m3u.close() # Invoke the command and log the output. output = util.command_output([command, m3u.name]) if output: log.debug(u'Output of {0}: {1}'.format(command, output)) ui.print_(u'Playing {0} {1}.'.format(len(paths), item_type))
def choose_match(path, items, cur_artist, cur_album, candidates, rec, color=True, quiet=False): """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, CHOICE_ASIS, or CHOICE_SKIP. """ if quiet: # No input; just make a decision. if rec == autotag.RECOMMEND_STRONG: dist, items, info = candidates[0] show_change(cur_artist, cur_album, items, info, dist, color) return info, items else: print_('Skipping: %s' % path) return CHOICE_SKIP # Loop until we have a choice. while True: # Choose from candidates, if available. if candidates: choice = choose_candidate(cur_artist, cur_album, candidates, rec, color) else: # Fallback: if either an error ocurred or no matches found. print_("No match found for:", path) sel = ui.input_options( "[U]se as-is, Skip, Enter manual search, or aBort?", ('u', 's', 'e', 'b'), 'u', 'Enter U, S, E, or B:' ) if sel == 'u': choice = CHOICE_ASIS elif sel == 'e': choice = CHOICE_MANUAL elif sel == 's': choice = CHOICE_SKIP elif sel == 'b': raise ImportAbort() # Choose which tags to use. if choice in (CHOICE_SKIP, CHOICE_ASIS): # Pass selection to main control flow. return choice elif choice is CHOICE_MANUAL: # Try again with manual search terms. search_artist, search_album = manual_search() else: # We have a candidate! Finish tagging. Here, choice is # an (info, items) pair as desired. return choice # Search for entered terms. try: _, _, candidates, rec = \ autotag.tag_album(items, search_artist, search_album) except autotag.AutotagError: candidates, rec = None, None
def _get_best_release(self, albums, beets_album): def value_or_na(value): return value if value is not None else 'N/A' def format_rym_album(album): return u'{0} - {1} ({2}, {3}, {4})'.format( value_or_na(album['artist']), value_or_na(album['album']), value_or_na(album['format']), value_or_na(album['label']), value_or_na(album['year'])) def set_url(): url = ui.input_('Enter rateyourmusic url:') return { 'href': url } print(u'\nFetching genre for album:\n {0} - {1}'.format( beets_album.albumartist, beets_album.album)) print(u'URL:\n %s' % albums[0]['href']) print(format_rym_album(albums[0])) res = ui.input_options(['apply', 'more candidates', 'set url', 'skip']) if res == 'a': return albums[0] elif res == 's': return set_url() elif res == 'k': return None else: id = 1 print(u'Candidates for {0} - {1} ({2}):'.format( beets_album.albumartist, beets_album.album, beets_album.year)) for album in albums: print(str(id) + u'. ' + format_rym_album(album)) id += 1 res = ui.input_options(['set url', 'skip'], numrange=(1, len(albums))) if res == 's': return set_url() elif res == 'k': return None return albums[res - 1]
def _get_best_release(self, albums, beets_album): def value_or_na(value): return value if value is not None else 'N/A' def format_rym_album(album): return u'{0} - {1} ({2}, {3}, {4})'.format( value_or_na(album['artist']), value_or_na(album['album']), value_or_na(album['format']), value_or_na(album['label']), value_or_na(album['year'])) def set_url(): url = ui.input_('Enter rateyourmusic url:') return {'href': url} print(u'\nFetching genre for album:\n {0} - {1}'.format( beets_album.albumartist, beets_album.album)) print(u'URL:\n %s' % albums[0]['href']) print(format_rym_album(albums[0])) res = ui.input_options(['apply', 'more candidates', 'set url', 'skip']) if res == 'a': return albums[0] elif res == 's': return set_url() elif res == 'k': return None else: id = 1 print(u'Candidates for {0} - {1} ({2}):'.format( beets_album.albumartist, beets_album.album, beets_album.year)) for album in albums: print(str(id) + u'. ' + format_rym_album(album)) id += 1 res = ui.input_options(['set url', 'skip'], numrange=(1, len(albums))) if res == 's': return set_url() elif res == 'k': return None return albums[res - 1]
def importer_edit_candidate(self, session, task): """Callback for invoking the functionality during an interactive import session on a *candidate*. The candidate's metadata is applied to the original items. """ # Prompt the user for a candidate. sel = ui.input_options([], numrange=(1, len(task.candidates))) # Force applying the candidate on the items. task.match = task.candidates[sel - 1] task.apply_metadata() return self.importer_edit(session, task)
def _exceeds_threshold(self, selection, command_str, open_args, item_type='track'): """Prompt user whether to abort if playlist exceeds threshold. If True, cancel playback. If False, execute play command. """ warning_threshold = config['play']['warning_threshold'].get(int) # Warn user before playing any huge playlists. if warning_threshold and len(selection) > warning_threshold: ui.print_(ui.colorize( 'text_warning', u'You are about to queue {0} {1}.'.format( len(selection), item_type))) if ui.input_options((u'Continue', u'Abort')) == 'a': return True return False
def on_import_task_before_choice(self, task, session): if hasattr(task, '_badfiles_checks_failed'): ui.print_('{} one or more files failed checks:'.format( ui.colorize('text_warning', 'BAD'))) for error in task._badfiles_checks_failed: for error_line in error: ui.print_(error_line) ui.print_() ui.print_('What would you like to do?') sel = ui.input_options(['aBort', 'skip', 'continue']) if sel == 's': return importer.action.SKIP elif sel == 'c': return None elif sel == 'b': raise importer.ImportAbort() else: raise Exception('Unexpected selection: {}'.format(sel))
def _exceeds_threshold(self, selection, command_str, open_args, item_type='track'): """Prompt user whether to abort if playlist exceeds threshold. If True, cancel playback. If False, execute play command. """ warning_threshold = config['play']['warning_threshold'].get(int) # Warn user before playing any huge playlists. if warning_threshold and len(selection) > warning_threshold: if len(selection) > 1: item_type += 's' ui.print_(ui.colorize( 'text_warning', u'You are about to queue {0} {1}.'.format( len(selection), item_type))) if ui.input_options((u'Continue', u'Abort')) == 'a': return True return False
def set_albumartist(self, default_albumartist): input_options = None if self.albumartist is None or self.albumartist == "": self.albumartist = default_albumartist input_options = (u'Accept as-is', u'Edit') ui.print_(u'Default Virtual Album Artist:') else: if self.albumartist == default_albumartist: input_options = (u'Accept as-is', u'Edit') else: input_options = (u'Accept as-is', u'Edit', u'Default value') ui.print_(u'Current Virtual Album Artist:') ui.print_(u' {}'.format(self.albumartist)) sel = ui.input_options(input_options) if sel == u'a': pass elif sel == u'e': self.albumartist = ui.input_(u'Virtual Album Artist:').strip() elif sel == u'd': self.albumartist = default_albumartist else: assert False
def resolve_duplicate(self, task): """Decide what to do when a new album or item seems similar to one that's already in the library. """ log.warn("This %s is already in the library!" % ("album" if task.is_album else "item")) if config["import"]["quiet"]: # In quiet mode, don't prompt -- just skip. log.info("Skipping.") sel = "s" else: sel = ui.input_options(("Skip new", "Keep both", "Remove old")) if sel == "s": # Skip new. task.set_choice(importer.action.SKIP) elif sel == "k": # Keep both. Do nothing; leave the choice intact. pass elif sel == "r": # Remove old. task.remove_duplicates = True else: assert False
def edit_objects(self, objs, fields): """Dump a set of Model objects to a file as text, ask the user to edit it, and apply any changes to the objects. Return a boolean indicating whether the edit succeeded. """ # Get the content to edit as raw data structures. old_data = [flatten(o, fields) for o in objs] # Set up a temporary file with the initial data for editing. new = NamedTemporaryFile(suffix='.yaml', delete=False) old_str = dump(old_data) new.write(old_str) new.close() # Loop until we have parseable data and the user confirms. try: while True: # Ask the user to edit the data. edit(new.name) # Read the data back after editing and check whether anything # changed. with open(new.name) as f: new_str = f.read() if new_str == old_str: ui.print_("No changes; aborting.") return False # Parse the updated data. try: new_data = load(new_str) except ParseError as e: ui.print_("Could not read data: {}".format(e)) if ui.input_yn("Edit again to fix? (Y/n)", True): continue else: return False # Show the changes. self.apply_data(objs, old_data, new_data) changed = False for obj in objs: changed |= ui.show_model_changes(obj) if not changed: ui.print_('No changes to apply.') return False # Confirm the changes. choice = ui.input_options( ('continue Editing', 'apply', 'cancel')) if choice == 'a': # Apply. return True elif choice == 'c': # Cancel. return False elif choice == 'e': # Keep editing. # Reset the temporary changes to the objects. for obj in objs: obj.read() continue # Remove the temporary file before returning. finally: os.remove(new.name)
def choose_candidate(cur_artist, cur_album, candidates, rec, color=True): """Given current metadata and a sorted list of (distance, candidate) pairs, ask the user for a selection of which candidate to use. Returns a pair (candidate, ordered) consisting of the the selected candidate and the associated track ordering. If user chooses to skip, use as-is, or search manually, returns CHOICE_SKIP, CHOICE_ASIS, or CHOICE_MANUAL instead of a tuple. """ # Is the change good enough? top_dist, _, _ = candidates[0] bypass_candidates = False if rec != autotag.RECOMMEND_NONE: dist, items, info = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. if not bypass_candidates: print_('Finding tags for "%s - %s".' % (cur_artist, cur_album)) print_('Candidates:') for i, (dist, items, info) in enumerate(candidates): print_('%i. %s - %s (%s)' % (i+1, info['artist'], info['album'], dist_string(dist, color))) # Ask the user for a choice. sel = ui.input_options( '# selection (default 1), Skip, Use as-is, ' 'Enter search, or aBort?', ('s', 'u', 'e', 'b'), '1', 'Enter a numerical selection, S, U, E, or B:', (1, len(candidates)) ) if sel == 's': return CHOICE_SKIP elif sel == 'u': return CHOICE_ASIS elif sel == 'e': return CHOICE_MANUAL elif sel == 'b': raise ImportAbort() else: # Numerical selection. dist, items, info = candidates[sel-1] bypass_candidates = False # Show what we're about to do. show_change(cur_artist, cur_album, items, info, dist, color) # Exact match => tag automatically. if rec == autotag.RECOMMEND_STRONG: return info, items # Ask for confirmation. sel = ui.input_options( '[A]pply, More candidates, Skip, Use as-is, ' 'Enter search, or aBort?', ('a', 'm', 's', 'u', 'e', 'b'), 'a', 'Enter A, M, S, U, E, or B:' ) if sel == 'a': return info, items elif sel == 'm': pass elif sel == 's': return CHOICE_SKIP elif sel == 'u': return CHOICE_ASIS elif sel == 'e': return CHOICE_MANUAL elif sel == 'b': raise ImportAbort()
def choose_candidate(candidates, singleton, rec, color, timid, cur_artist=None, cur_album=None, item=None, itemcount=None, per_disc_numbering=False): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). Candidates are either AlbumMatch or TrackMatch objects depending on `singleton`. for albums, `cur_artist`, `cur_album`, and `itemcount` must be provided. For singletons, `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate (an AlbumMatch/TrackMatch object). """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: if singleton: print_("No matching recordings found.") opts = ('Use as-is', 'Skip', 'Enter search', 'enter Id', 'aBort') else: print_( "No matching release found for {0} tracks.".format(itemcount)) print_('For help, see: ' 'https://github.com/sampsyo/beets/wiki/FAQ#wiki-nomatch') opts = ('Use as-is', 'as Tracks', 'Skip', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, color=color) if sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 's': return importer.action.SKIP elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: assert False # Is the change good enough? bypass_candidates = False if rec != autotag.RECOMMEND_NONE: match = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. if not bypass_candidates: # Display list of candidates. if singleton: print_('Finding tags for track "%s - %s".' % (item.artist, item.title)) print_('Candidates:') for i, match in enumerate(candidates): print_('%i. %s - %s (%s)' % (i + 1, match.info.artist, match.info.title, dist_string(match.distance, color))) else: print_('Finding tags for album "%s - %s".' % (cur_artist, cur_album)) print_('Candidates:') for i, match in enumerate(candidates): line = '%i. %s - %s' % (i + 1, match.info.artist, match.info.album) # Label and year disambiguation, if available. label, year = None, None if match.info.label: label = match.info.label if match.info.year: year = unicode(match.info.year) if label and year: line += u' [%s, %s]' % (label, year) elif label: line += u' [%s]' % label elif year: line += u' [%s]' % year line += ' (%s)' % dist_string(match.distance, color) # Point out the partial matches. if match.extra_items or match.extra_tracks: warning = PARTIAL_MATCH_MESSAGE if color: warning = ui.colorize('yellow', warning) line += u' %s' % warning print_(line) # Ask the user for a choice. if singleton: opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, numrange=(1, len(candidates)), color=color) if sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 'e': return importer.action.MANUAL elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: # Numerical selection. if singleton: match = candidates[sel - 1] else: match = candidates[sel - 1] bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, match, color) else: show_change(cur_artist, cur_album, match, color, per_disc_numbering) # Exact match => tag automatically if we're not in timid mode. if rec == autotag.RECOMMEND_STRONG and not timid: return match # Ask for confirmation. if singleton: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, color=color) if sel == 'a': return match elif sel == 'm': pass elif sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID
def choose_candidate(candidates, singleton, rec, color, timid, cur_artist=None, cur_album=None, item=None): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). For albums, the candidates are `(dist, items, info)` triples and `cur_artist` and `cur_album` must be provided. For singletons, the candidates are `(dist, info)` pairs and `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate. For albums, a candidate is a `(info, items)` pair; for items, it is just an `info` dictionary. """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: print_("No match found.") if singleton: opts = ('Use as-is', 'Skip', 'Enter search', 'enter Id', 'aBort') else: opts = ('Use as-is', 'as Tracks', 'Skip', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, color=color) if sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 's': return importer.action.SKIP elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: assert False # Is the change good enough? bypass_candidates = False if rec != autotag.RECOMMEND_NONE: if singleton: dist, info = candidates[0] else: dist, items, info = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. if not bypass_candidates: # Display list of candidates. if singleton: print_('Finding tags for track "%s - %s".' % (item.artist, item.title)) print_('Candidates:') for i, (dist, info) in enumerate(candidates): print_('%i. %s - %s (%s)' % (i+1, info['artist'], info['title'], dist_string(dist, color))) else: print_('Finding tags for album "%s - %s".' % (cur_artist, cur_album)) print_('Candidates:') for i, (dist, items, info) in enumerate(candidates): line = '%i. %s - %s' % (i+1, info['artist'], info['album']) # Label and year disambiguation, if available. label, year = None, None if 'label' in info: label = info['label'] if 'year' in info and info['year']: year = unicode(info['year']) if label and year: line += u' [%s, %s]' % (label, year) elif label: line += u' [%s]' % label elif year: line += u' [%s]' % year line += ' (%s)' % dist_string(dist, color) print_(line) # Ask the user for a choice. if singleton: opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, numrange=(1, len(candidates)), color=color) if sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 'e': return importer.action.MANUAL elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: # Numerical selection. if singleton: dist, info = candidates[sel-1] else: dist, items, info = candidates[sel-1] bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, info, dist, color) else: show_change(cur_artist, cur_album, items, info, dist, color) # Exact match => tag automatically if we're not in timid mode. if rec == autotag.RECOMMEND_STRONG and not timid: if singleton: return info else: return info, items # Ask for confirmation. if singleton: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, color=color) if sel == 'a': if singleton: return info else: return info, items elif sel == 'm': pass elif sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID
def edit_objects(self, objs, fields): """Dump a set of Model objects to a file as text, ask the user to edit it, and apply any changes to the objects. Return a boolean indicating whether the edit succeeded. """ # Get the content to edit as raw data structures. old_data = [flatten(o, fields) for o in objs] # Set up a temporary file with the initial data for editing. if six.PY2: new = NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) else: new = NamedTemporaryFile(mode='w', suffix='.yaml', delete=False, encoding='utf-8') old_str = dump(old_data) new.write(old_str) if six.PY2: old_str = old_str.decode('utf-8') new.close() # Loop until we have parseable data and the user confirms. try: while True: # Ask the user to edit the data. edit(new.name, self._log) # Read the data back after editing and check whether anything # changed. with codecs.open(new.name, encoding='utf-8') as f: new_str = f.read() if new_str == old_str: ui.print_(u"No changes; aborting.") return False # Parse the updated data. try: new_data = load(new_str) except ParseError as e: ui.print_(u"Could not read data: {}".format(e)) if ui.input_yn(u"Edit again to fix? (Y/n)", True): continue else: return False # Show the changes. # If the objects are not on the DB yet, we need a copy of their # original state for show_model_changes. objs_old = [ deepcopy(obj) if not obj._db else None for obj in objs ] self.apply_data(objs, old_data, new_data) changed = False for obj, obj_old in zip(objs, objs_old): changed |= ui.show_model_changes(obj, obj_old) if not changed: ui.print_(u'No changes to apply.') return False # Confirm the changes. choice = ui.input_options( (u'continue Editing', u'apply', u'cancel')) if choice == u'a': # Apply. return True elif choice == u'c': # Cancel. return False elif choice == u'e': # Keep editing. # Reset the temporary changes to the objects. for obj in objs: obj.read() continue # Remove the temporary file before returning. finally: os.remove(new.name)
def play_music(self, lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist. """ command_str = config['play']['command'].get() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() if relative_to: relative_to = util.normpath(relative_to) # Perform search by album and add folders rather than tracks to # playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] sort = lib.get_default_album_sort() for album in selection: if use_folders: paths.append(album.item_dir()) else: paths.extend(item.path for item in sort.sort(album.items())) item_type = 'album' # Perform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] item_type = 'track' item_type += 's' if len(selection) > 1 else '' if not selection: ui.print_(ui.colorize('text_warning', 'No {0} to play.'.format(item_type))) return # Warn user before playing any huge playlists. if len(selection) > 100: ui.print_(ui.colorize( 'text_warning', 'You are about to queue {0} {1}.'.format(len(selection), item_type) )) if ui.input_options(('Continue', 'Abort')) == 'a': return # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) for item in paths: if relative_to: m3u.write(relpath(item, relative_to) + b'\n') else: m3u.write(item + b'\n') m3u.close() ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) try: util.interactive_open(m3u.name, command_str) except OSError as exc: raise ui.UserError("Could not play the music playlist: " "{0}".format(exc)) finally: util.remove(m3u.name)
def play_music(self, lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist, at request insert optional arguments. """ command_str = config['play']['command'].get() if not command_str: command_str = util.open_anything() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() raw = config['play']['raw'].get(bool) warning_threshold = config['play']['warning_threshold'].get(int) # We use -2 as a default value for warning_threshold to detect if it is # set or not. We can't use a falsey value because it would have an # actual meaning in the configuration of this plugin, and we do not use # -1 because some people might use it as a value to obtain no warning, # which wouldn't be that bad of a practice. if warning_threshold == -2: # if warning_threshold has not been set by user, look for # warning_treshold, to preserve backwards compatibility. See #1803. # warning_treshold has the correct default value of 100. warning_threshold = config['play']['warning_treshold'].get(int) if relative_to: relative_to = util.normpath(relative_to) # Add optional arguments to the player command. if opts.args: if ARGS_MARKER in command_str: command_str = command_str.replace(ARGS_MARKER, opts.args) else: command_str = u"{} {}".format(command_str, opts.args) # Perform search by album and add folders rather than tracks to # playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] sort = lib.get_default_album_sort() for album in selection: if use_folders: paths.append(album.item_dir()) else: paths.extend(item.path for item in sort.sort(album.items())) item_type = 'album' # Perform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] if relative_to: paths = [relpath(path, relative_to) for path in paths] item_type = 'track' item_type += 's' if len(selection) > 1 else '' if not selection: ui.print_( ui.colorize('text_warning', u'No {0} to play.'.format(item_type))) return # Warn user before playing any huge playlists. if warning_threshold and len(selection) > warning_threshold: ui.print_( ui.colorize( 'text_warning', u'You are about to queue {0} {1}.'.format( len(selection), item_type))) if ui.input_options(('Continue', 'Abort')) == 'a': return ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) if raw: open_args = paths else: open_args = [self._create_tmp_playlist(paths)] self._log.debug(u'executing command: {} {}', command_str, b' '.join(open_args)) try: util.interactive_open(open_args, command_str) except OSError as exc: raise ui.UserError("Could not play the query: {0}".format(exc))
def play_music(self, lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist, at request insert optional arguments. """ command_str = config['play']['command'].get() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() raw = config['play']['raw'].get(bool) warning_treshold = config['play']['warning_treshold'].get(int) if relative_to: relative_to = util.normpath(relative_to) # Add optional arguments to the player command. if opts.args: if ARGS_MARKER in command_str: command_str = command_str.replace(ARGS_MARKER, opts.args) else: command_str = "{} {}".format(command_str, opts.args) # Perform search by album and add folders rather than tracks to # playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] sort = lib.get_default_album_sort() for album in selection: if use_folders: paths.append(album.item_dir()) else: paths.extend(item.path for item in sort.sort(album.items())) item_type = 'album' # Perform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] if relative_to: paths = [relpath(path, relative_to) for path in paths] item_type = 'track' item_type += 's' if len(selection) > 1 else '' if not selection: ui.print_(ui.colorize('text_warning', 'No {0} to play.'.format(item_type))) return # Warn user before playing any huge playlists. if warning_treshold and len(selection) > warning_treshold: ui.print_(ui.colorize( 'text_warning', 'You are about to queue {0} {1}.'.format(len(selection), item_type) )) if ui.input_options(('Continue', 'Abort')) == 'a': return ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) if raw: open_args = paths else: open_args = [self._create_tmp_playlist(paths)] self._log.debug('executing command: {} {}', command_str, b' '.join(open_args)) try: util.interactive_open(open_args, command_str) except OSError as exc: raise ui.UserError("Could not play the music playlist: " "{0}".format(exc)) finally: if not raw: self._log.debug('Removing temporary playlist: {}', open_args[0]) util.remove(open_args[0])
def play_music(lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist. """ command_str = config["play"]["command"].get() use_folders = config["play"]["use_folders"].get(bool) relative_to = config["play"]["relative_to"].get() if relative_to: relative_to = util.normpath(relative_to) if command_str: command = shlex.split(command_str) else: # If a command isn't set, then let the OS decide how to open the # playlist. sys_name = platform.system() if sys_name == "Darwin": command = ["open"] elif sys_name == "Windows": command = ["start"] else: # If not Mac or Windows, then assume Unixy. command = ["xdg-open"] # Preform search by album and add folders rather then tracks to playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] for album in selection: if use_folders: paths.append(album.item_dir()) else: # TODO use core's sorting functionality paths.extend([item.path for item in sorted(album.items(), key=lambda item: (item.disc, item.track))]) item_type = "album" # Preform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] item_type = "track" item_type += "s" if len(selection) > 1 else "" if not selection: ui.print_(ui.colorize("yellow", "No {0} to play.".format(item_type))) return # Warn user before playing any huge playlists. if len(selection) > 100: ui.print_(ui.colorize("yellow", "You are about to queue {0} {1}.".format(len(selection), item_type))) if ui.input_options(("Continue", "Abort")) == "a": return # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile("w", suffix=".m3u", delete=False) for item in paths: if relative_to: m3u.write(relpath(item, relative_to) + "\n") else: m3u.write(item + "\n") m3u.close() command.append(m3u.name) # Invoke the command and log the output. output = util.command_output(command) if output: log.debug(u"Output of {0}: {1}".format(command[0], output)) ui.print_(u"Playing {0} {1}.".format(len(selection), item_type)) util.remove(m3u.name)
def edit_objects(self, objs, fields): """Dump a set of Model objects to a file as text, ask the user to edit it, and apply any changes to the objects. Return a boolean indicating whether the edit succeeded. """ # Get the content to edit as raw data structures. old_data = [flatten(o, fields) for o in objs] # Set up a temporary file with the initial data for editing. if six.PY2: new = NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) else: new = NamedTemporaryFile(mode='w', suffix='.yaml', delete=False, encoding='utf-8') old_str = dump(old_data) new.write(old_str) if six.PY2: old_str = old_str.decode('utf-8') new.close() # Loop until we have parseable data and the user confirms. try: while True: # Ask the user to edit the data. edit(new.name, self._log) # Read the data back after editing and check whether anything # changed. with codecs.open(new.name, encoding='utf-8') as f: new_str = f.read() if new_str == old_str: ui.print_(u"No changes; aborting.") return False # Parse the updated data. try: new_data = load(new_str) except ParseError as e: ui.print_(u"Could not read data: {}".format(e)) if ui.input_yn(u"Edit again to fix? (Y/n)", True): continue else: return False # Show the changes. # If the objects are not on the DB yet, we need a copy of their # original state for show_model_changes. objs_old = [obj.copy() if obj.id < 0 else None for obj in objs] self.apply_data(objs, old_data, new_data) changed = False for obj, obj_old in zip(objs, objs_old): changed |= ui.show_model_changes(obj, obj_old) if not changed: ui.print_(u'No changes to apply.') return False # Confirm the changes. choice = ui.input_options( (u'continue Editing', u'apply', u'cancel') ) if choice == u'a': # Apply. return True elif choice == u'c': # Cancel. return False elif choice == u'e': # Keep editing. # Reset the temporary changes to the objects. I we have a # copy from above, use that, else reload from the database. objs = [(old_obj or obj) for old_obj, obj in zip(objs_old, objs)] for obj in objs: if not obj.id < 0: obj.load() continue # Remove the temporary file before returning. finally: os.remove(new.name)
def play_music(lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist. """ command = config['play']['command'].get() is_debug = config['play']['debug'].get() # If a command isn't set then let the OS decide how to open the playlist. if not command: sys_name = platform.system() if sys_name == 'Darwin': command = 'open' elif sys_name == 'Windows': command = 'start' else: # If not Mac or Win then assume Linux(or posix based). command = 'xdg-open' # Preform search by album and add folders rather then tracks to playlist. if opts.album: albums = lib.albums(ui.decargs(args)) paths = [] for album in albums: paths.append(album.item_dir()) itemType = 'album' # Preform item query and add tracks to playlist. else: paths = [item.path for item in lib.items(ui.decargs(args))] itemType = 'track' itemType += 's' if len(paths) > 1 else '' if not paths: ui.print_(ui.colorize('yellow', 'no {0} to play.'.format(itemType))) return # Warn user before playing any huge playlists. if len(paths) > 100: ui.print_(ui.colorize('yellow', 'do you really want to play {0} {1}?' .format(len(paths), itemType))) if ui.input_options(('Continue', 'Abort')) == 'a': return # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) for item in paths: m3u.write(item + '\n') m3u.close() # Prevent player output from poluting our console(unless debug is on). if not is_debug: FNULL = open(os.devnull, 'w') subprocess.Popen([command, m3u.name], stdout=FNULL, stderr=subprocess.STDOUT) FNULL.close() else: subprocess.Popen([command, m3u.name]) ui.print_('playing {0} {1}.'.format(len(paths), itemType))
def play_music(self, lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist, at request insert optional arguments. """ command_str = config['play']['command'].get() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() if relative_to: relative_to = util.normpath(relative_to) # Add optional arguments to the player command. if opts.args: command_str = "{} {}".format(command_str, opts.args) # Perform search by album and add folders rather than tracks to # playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] sort = lib.get_default_album_sort() for album in selection: if use_folders: paths.append(album.item_dir()) else: paths.extend(item.path for item in sort.sort(album.items())) item_type = 'album' # Perform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] item_type = 'track' item_type += 's' if len(selection) > 1 else '' if not selection: ui.print_( ui.colorize('text_warning', 'No {0} to play.'.format(item_type))) return # Warn user before playing any huge playlists. if len(selection) > 100: ui.print_( ui.colorize( 'text_warning', 'You are about to queue {0} {1}.'.format( len(selection), item_type))) if ui.input_options(('Continue', 'Abort')) == 'a': return # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) for item in paths: if relative_to: m3u.write(relpath(item, relative_to) + b'\n') else: m3u.write(item + b'\n') m3u.close() ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) self._log.debug('executing command: {} {}', command_str, m3u.name) try: util.interactive_open(m3u.name, command_str) except OSError as exc: raise ui.UserError("Could not play the music playlist: " "{0}".format(exc)) finally: util.remove(m3u.name)
def choose_candidate(candidates, singleton, rec, color, interactive_autotag, cur_artist=None, cur_album=None, item=None): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). For albums, the candidates are `(dist, items, info)` triples and `cur_artist` and `cur_album` must be provided. For singletons, the candidates are `(dist, info)` pairs and `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate. For albums, a candidate is a `(info, items)` pair; for items, it is just an `info` dictionary. """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: print_("No match found.") if singleton: opts = ('Use as-is', 'Skip', 'Enter search', 'aBort') else: opts = ('Use as-is', 'as Tracks', 'Skip', 'Enter search', 'aBort') sel = ui.input_options(opts) if sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 's': return importer.action.SKIP elif sel == 'b': raise importer.ImportAbort() else: assert False # Is the change good enough? bypass_candidates = False if rec != autotag.RECOMMEND_NONE: if singleton: dist, info = candidates[0] else: dist, items, info = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. if not bypass_candidates: # Display list of candidates. if singleton: print_('Finding tags for track "%s - %s".' % (item.artist, item.title)) print_('Candidates:') for i, (dist, info) in enumerate(candidates): print_('%i. %s - %s (%s)' % (i + 1, info['artist'], info['title'], dist_string(dist, color))) else: print_('Finding tags for album "%s - %s".' % (cur_artist, cur_album)) print_('Candidates:') for i, (dist, items, info) in enumerate(candidates): print_('%i. %s - %s (%s)' % (i + 1, info['artist'], info['album'], dist_string(dist, color))) # Ask the user for a choice. if singleton: opts = ('Skip', 'Use as-is', 'Enter search', 'aBort') else: opts = ('Skip', 'Use as-is', 'as Tracks', 'Enter search', 'aBort') sel = ui.input_options(opts, numrange=(1, len(candidates))) if sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 'e': return importer.action.MANUAL elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'b': raise importer.ImportAbort() else: # Numerical selection. if singleton: dist, info = candidates[sel - 1] else: dist, items, info = candidates[sel - 1] bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, info, dist, color) else: show_change(cur_artist, cur_album, items, info, dist, color) # Exact match => tag automatically if no interactive tagging is requested. if rec == autotag.RECOMMEND_STRONG and not interactive_autotag: if singleton: return info else: return info, items # Ask for confirmation. if singleton: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'Enter search', 'aBort') else: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'as Tracks', 'Enter search', 'aBort') sel = ui.input_options(opts) if sel == 'a': if singleton: return info else: return info, items elif sel == 'm': pass elif sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 'b': raise importer.ImportAbort()
def play_music(self, lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist, at request insert optional arguments. """ command_str = config["play"]["command"].get() use_folders = config["play"]["use_folders"].get(bool) relative_to = config["play"]["relative_to"].get() raw = config["play"]["raw"].get(bool) if relative_to: relative_to = util.normpath(relative_to) # Add optional arguments to the player command. if opts.args: if ARGS_MARKER in command_str: command_str = command_str.replace(ARGS_MARKER, opts.args) else: command_str = "{} {}".format(command_str, opts.args) # Perform search by album and add folders rather than tracks to # playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] sort = lib.get_default_album_sort() for album in selection: if use_folders: paths.append(album.item_dir()) else: paths.extend(item.path for item in sort.sort(album.items())) item_type = "album" # Perform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] if relative_to: paths = [relpath(path, relative_to) for path in paths] item_type = "track" item_type += "s" if len(selection) > 1 else "" if not selection: ui.print_(ui.colorize("text_warning", "No {0} to play.".format(item_type))) return # Warn user before playing any huge playlists. if len(selection) > 100: ui.print_(ui.colorize("text_warning", "You are about to queue {0} {1}.".format(len(selection), item_type))) if ui.input_options(("Continue", "Abort")) == "a": return ui.print_("Playing {0} {1}.".format(len(selection), item_type)) if raw: open_args = paths else: open_args = self._create_tmp_playlist(paths) self._log.debug("executing command: {} {}", command_str, b'"' + b" ".join(open_args) + b'"') try: util.interactive_open(open_args, command_str) except OSError as exc: raise ui.UserError("Could not play the music playlist: " "{0}".format(exc)) finally: if not raw: self._log.debug("Removing temporary playlist: {}", open_args[0]) util.remove(open_args[0])
def play_music(lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist. """ command_str = config['play']['command'].get() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() if relative_to: relative_to = util.normpath(relative_to) if command_str: command = shlex.split(command_str) else: # If a command isn't set, then let the OS decide how to open the # playlist. sys_name = platform.system() if sys_name == 'Darwin': command = ['open'] elif sys_name == 'Windows': command = ['start'] else: # If not Mac or Windows, then assume Unixy. command = ['xdg-open'] # Preform search by album and add folders rather then tracks to playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] for album in selection: if use_folders: paths.append(album.item_dir()) else: # TODO use core's sorting functionality paths.extend([item.path for item in sorted( album.items(), key=lambda item: (item.disc, item.track))]) item_type = 'album' # Preform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] item_type = 'track' item_type += 's' if len(selection) > 1 else '' if not selection: ui.print_(ui.colorize('yellow', 'No {0} to play.'.format(item_type))) return # Warn user before playing any huge playlists. if len(selection) > 100: ui.print_(ui.colorize( 'yellow', 'You are about to queue {0} {1}.'.format(len(selection), item_type) )) if ui.input_options(('Continue', 'Abort')) == 'a': return # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) for item in paths: if relative_to: m3u.write(relpath(item, relative_to) + '\n') else: m3u.write(item + '\n') m3u.close() command.append(m3u.name) # Invoke the command and log the output. output = util.command_output(command) if output: log.debug(u'Output of {0}: {1}'.format( util.displayable_path(command[0]), output.decode('utf8', 'ignore'), )) ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) util.remove(m3u.name)
def suggest_removal(self, item): """Prompts the user to delete the original path the item was imported from.""" if not item[ 'source_path'] or item.mb_albumid in self.stop_suggestions_for_albums: return if os.path.isdir(item['source_path']): # We ask the user whether they'd like to delete the item's source directory delete = input_yn( "The item:\n{path}\nis originated in the directory:\n{source}\n" "Would you like to delete the source directory of this item?". format(path=colorize_text("text_warning", item.path.decode('utf-8')), source=colorize_text( "text_warning", item['source_path'].decode('utf-8'))), require=True) if delete: self._log.info( "Deleting the item's source which is a directory: %s", item['source_path']) rmtree(item['source_path']) elif os.path.isfile(item['source_path']): # We ask the user whether they'd like to delete the item's source directory print("The item:\n{path}\nis originated in from:\n{source}\n" "What would you like to do?".format( path=colorize_text("text_warning", item.path.decode('utf-8')), source=colorize_text( "text_warning", item['source_path'].decode('utf-8')))) resp = input_options( [ "Delete the item's source", "Recursively delete the source's directory", "do Nothing", "do nothing and Stop suggesting to delete items from this album" ], require=True, ) if resp == 'd': self._log.info("Deleting the item's source file: %s", item['source_path']) os.remove(item['source_path']) elif resp == 'r': source_dir = os.path.dirname(item['source_path']) self._log.info( "Searching for other items with a source_path attr containing: %s", source_dir) # NOTE: I'm not sure why, but we need to escape it twice otherwise the search fails escaped_source_dir = re.escape( re.escape(source_dir.decode("utf-8"))) source_dir_query = "source_path::^{}/*".format( escaped_source_dir) print( "Doing so will delete the following items' sources as well:" ) for searched_item in item._db.items(source_dir_query): print( colorize_text("text_warning", searched_item['path'].decode('utf-8'))) print("Would you like to continue?") continue_resp = input_options( ["Yes", "delete None", "delete just the File"], require=False, # Yes is the a default ) if continue_resp == 'y': self._log.info("Deleting the item's source directory: %s", source_dir) rmtree(source_dir) elif continue_resp == 'n': self._log.info("doing nothing - aborting hook function") return elif continue_resp == 'f': self._log.info( "removing just the item's original source: %s", item['source_path']) os.remove(item['source_path']) elif resp == 's': self.stop_suggestions_for_albums.append(item.mb_albumid) else: self._log.info("Doing nothing")
def choose_candidate(candidates, singleton, rec, cur_artist=None, cur_album=None, item=None, itemcount=None): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). Candidates are either AlbumMatch or TrackMatch objects depending on `singleton`. for albums, `cur_artist`, `cur_album`, and `itemcount` must be provided. For singletons, `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate (an AlbumMatch/TrackMatch object). """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: if singleton: print_("No matching recordings found.") opts = ('Use as-is', 'Skip', 'Enter search', 'enter Id', 'aBort') else: print_( "No matching release found for {0} tracks.".format(itemcount)) print_('For help, see: ' 'http://beets.readthedocs.org/en/latest/faq.html#nomatch') opts = ('Use as-is', 'as Tracks', 'Skip', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts) if sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 's': return importer.action.SKIP elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: assert False # Is the change good enough? bypass_candidates = False if rec != recommendation.none: match = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. require = rec <= recommendation.low if not bypass_candidates: # Display list of candidates. print_(u'Finding tags for {0} "{1} - {2}".'.format( u'track' if singleton else u'album', item.artist if singleton else cur_artist, item.title if singleton else cur_album, )) print_(u'Candidates:') for i, match in enumerate(candidates): # Index, metadata, and distance. line = [ u'{0}.'.format(i + 1), u'{0} - {1}'.format( match.info.artist, match.info.title if singleton else match.info.album, ), u'({0})'.format(dist_string(match.distance)), ] # Penalties. penalties = penalty_string(match.distance, 3) if penalties: line.append(penalties) # Disambiguation disambig = disambig_string(match.info) if disambig: line.append(ui.colorize('lightgray', '(%s)' % disambig)) print_(' '.join(line)) # Ask the user for a choice. if singleton: opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, numrange=(1, len(candidates))) if sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 'e': return importer.action.MANUAL elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: # Numerical selection. match = candidates[sel - 1] if sel != 1: # When choosing anything but the first match, # disable the default action. require = True bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, match) else: show_change(cur_artist, cur_album, match) # Exact match => tag automatically if we're not in timid mode. if rec == recommendation.strong and not config['import']['timid']: return match # Ask for confirmation. if singleton: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') default = config['import']['default_action'].as_choice({ 'apply': 'a', 'skip': 's', 'asis': 'u', 'none': None, }) if default is None: require = True sel = ui.input_options(opts, require=require, default=default) if sel == 'a': return match elif sel == 'm': pass elif sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID
def choose_candidate(candidates, singleton, rec, cur_artist=None, cur_album=None, item=None, itemcount=None): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). Candidates are either AlbumMatch or TrackMatch objects depending on `singleton`. for albums, `cur_artist`, `cur_album`, and `itemcount` must be provided. For singletons, `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate (an AlbumMatch/TrackMatch object). """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: if singleton: print_("No matching recordings found.") opts = ('Use as-is', 'Skip', 'Enter search', 'enter Id', 'aBort') else: print_("No matching release found for {0} tracks." .format(itemcount)) print_('For help, see: ' 'http://beets.readthedocs.org/en/latest/faq.html#nomatch') opts = ('Use as-is', 'as Tracks', 'Group albums', 'Skip', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts) if sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 's': return importer.action.SKIP elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID elif sel == 'g': return importer.action.ALBUMS else: assert False # Is the change good enough? bypass_candidates = False if rec != recommendation.none: match = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. require = rec <= recommendation.low if not bypass_candidates: # Display list of candidates. print_(u'Finding tags for {0} "{1} - {2}".'.format( u'track' if singleton else u'album', item.artist if singleton else cur_artist, item.title if singleton else cur_album, )) print_(u'Candidates:') for i, match in enumerate(candidates): # Index, metadata, and distance. line = [ u'{0}.'.format(i + 1), u'{0} - {1}'.format( match.info.artist, match.info.title if singleton else match.info.album, ), u'({0})'.format(dist_string(match.distance)), ] # Penalties. penalties = penalty_string(match.distance, 3) if penalties: line.append(penalties) # Disambiguation disambig = disambig_string(match.info) if disambig: line.append(ui.colorize('lightgray', '(%s)' % disambig)) print_(' '.join(line)) # Ask the user for a choice. if singleton: opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Skip', 'Use as-is', 'as Tracks', 'Group albums', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, numrange=(1, len(candidates))) if sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 'm': pass elif sel == 'e': return importer.action.MANUAL elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID elif sel == 'g': return importer.action.ALBUMS else: # Numerical selection. match = candidates[sel - 1] if sel != 1: # When choosing anything but the first match, # disable the default action. require = True bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, match) else: show_change(cur_artist, cur_album, match) # Exact match => tag automatically if we're not in timid mode. if rec == recommendation.strong and not config['import']['timid']: return match # Ask for confirmation. if singleton: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'as Tracks', 'Group albums', 'Enter search', 'enter Id', 'aBort') default = config['import']['default_action'].as_choice({ 'apply': 'a', 'skip': 's', 'asis': 'u', 'none': None, }) if default is None: require = True sel = ui.input_options(opts, require=require, default=default) if sel == 'a': return match elif sel == 'g': return importer.action.ALBUMS elif sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID
def play_music(lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist. """ command_str = config['play']['command'].get() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() if relative_to: relative_to = util.normpath(relative_to) if command_str: command = shlex.split(command_str) else: # If a command isn't set, then let the OS decide how to open the # playlist. sys_name = platform.system() if sys_name == 'Darwin': command = ['open'] elif sys_name == 'Windows': command = ['start'] else: # If not Mac or Windows, then assume Unixy. command = ['xdg-open'] # Preform search by album and add folders rather then tracks to playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] for album in selection: if use_folders: paths.append(album.item_dir()) else: # TODO use core's sorting functionality paths.extend([ item.path for item in sorted(album.items(), key=lambda item: (item.disc, item.track)) ]) item_type = 'album' # Preform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] item_type = 'track' item_type += 's' if len(selection) > 1 else '' if not selection: ui.print_(ui.colorize('yellow', 'No {0} to play.'.format(item_type))) return # Warn user before playing any huge playlists. if len(selection) > 100: ui.print_( ui.colorize( 'yellow', 'You are about to queue {0} {1}.'.format( len(selection), item_type))) if ui.input_options(('Continue', 'Abort')) == 'a': return # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) for item in paths: if relative_to: m3u.write(relpath(item, relative_to) + '\n') else: m3u.write(item + '\n') m3u.close() command.append(m3u.name) # Invoke the command and log the output. output = util.command_output(command) if output: log.debug(u'Output of {0}: {1}'.format(command[0], output)) ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type))
def choose_candidate(candidates, singleton, rec, color, timid, cur_artist=None, cur_album=None, item=None, itemcount=None, per_disc_numbering=False): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). For albums, the candidates are `(dist, items, info)` triples and `cur_artist`, `cur_album`, and `itemcount` must be provided. For singletons, the candidates are `(dist, info)` pairs and `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate. For albums, a candidate is a `(info, items)` pair; for items, it is just a TrackInfo object. """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: if singleton: print_("No matching recordings found.") opts = ('Use as-is', 'Skip', 'Enter search', 'enter Id', 'aBort') else: print_("No matching release found for {0} tracks." .format(itemcount)) print_('For help, see: ' 'https://github.com/sampsyo/beets/wiki/FAQ#wiki-nomatch') opts = ('Use as-is', 'as Tracks', 'Skip', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, color=color) if sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 's': return importer.action.SKIP elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: assert False # Is the change good enough? bypass_candidates = False if rec != autotag.RECOMMEND_NONE: if singleton: dist, info = candidates[0] else: dist, items, info = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. if not bypass_candidates: # Display list of candidates. if singleton: print_('Finding tags for track "%s - %s".' % (item.artist, item.title)) print_('Candidates:') for i, (dist, info) in enumerate(candidates): print_('%i. %s - %s (%s)' % (i+1, info.artist, info.title, dist_string(dist, color))) else: print_('Finding tags for album "%s - %s".' % (cur_artist, cur_album)) print_('Candidates:') for i, (dist, items, info) in enumerate(candidates): line = '%i. %s - %s' % (i+1, info.artist, info.album) # Label and year disambiguation, if available. label, year = None, None if info.label: label = info.label if info.year: year = unicode(info.year) if label and year: line += u' [%s, %s]' % (label, year) elif label: line += u' [%s]' % label elif year: line += u' [%s]' % year line += ' (%s)' % dist_string(dist, color) # Point out the partial matches. if None in items: warning = PARTIAL_MATCH_MESSAGE if color: warning = ui.colorize('yellow', warning) line += u' %s' % warning print_(line) # Ask the user for a choice. if singleton: opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, numrange=(1, len(candidates)), color=color) if sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 'e': return importer.action.MANUAL elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: # Numerical selection. if singleton: dist, info = candidates[sel-1] else: dist, items, info = candidates[sel-1] bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, info, dist, color) else: show_change(cur_artist, cur_album, items, info, dist, color, per_disc_numbering) # Exact match => tag automatically if we're not in timid mode. if rec == autotag.RECOMMEND_STRONG and not timid: if singleton: return info else: return info, items # Ask for confirmation. if singleton: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, color=color) if sel == 'a': if singleton: return info else: return info, items elif sel == 'm': pass elif sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID
def choose_candidate(candidates, singleton, rec, cur_artist=None, cur_album=None, item=None, itemcount=None): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). Candidates are either AlbumMatch or TrackMatch objects depending on `singleton`. for albums, `cur_artist`, `cur_album`, and `itemcount` must be provided. For singletons, `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate (an AlbumMatch/TrackMatch object). """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: if singleton: print_("No matching recordings found.") opts = ('Use as-is', 'Skip', 'Enter search', 'enter Id', 'aBort') else: print_( "No matching release found for {0} tracks.".format(itemcount)) print_('For help, see: ' 'https://github.com/sampsyo/beets/wiki/FAQ#wiki-nomatch') opts = ('Use as-is', 'as Tracks', 'Skip', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts) if sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 's': return importer.action.SKIP elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: assert False # Is the change good enough? bypass_candidates = False if rec != recommendation.none: match = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. require = rec <= recommendation.low if not bypass_candidates: # Display list of candidates. if singleton: print_('Finding tags for track "%s - %s".' % (item.artist, item.title)) print_('Candidates:') for i, match in enumerate(candidates): print_('%i. %s - %s (%s)' % (i + 1, match.info.artist, match.info.title, dist_string(match.distance))) else: print_('Finding tags for album "%s - %s".' % (cur_artist, cur_album)) print_('Candidates:') for i, match in enumerate(candidates): line = [ '%i. %s - %s (%s)' % (i + 1, match.info.artist, match.info.album, dist_string(match.distance)) ] # Point out the partial matches. if match.extra_items or match.extra_tracks: line.append( ui.colorize('yellow', PARTIAL_MATCH_MESSAGE)) # Sources other than MusicBrainz. source = match.info.data_source if source != 'MusicBrainz': line.append(ui.colorize('turquoise', '(%s)' % source)) disambig = disambig_string(match.info) if disambig: line.append(ui.colorize('lightgray', '(%s)' % disambig)) print_(' '.join(line)) # Ask the user for a choice. if singleton: opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, numrange=(1, len(candidates))) if sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 'e': return importer.action.MANUAL elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: # Numerical selection. match = candidates[sel - 1] if sel != 1: # When choosing anything but the first match, # disable the default action. require = True bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, match) else: show_change(cur_artist, cur_album, match) # Exact match => tag automatically if we're not in timid mode. if rec == recommendation.strong and not config['import']['timid']: return match # Ask for confirmation. if singleton: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') default = config['import']['default_action'].as_choice({ 'apply': 'a', 'skip': 's', 'asis': 'u', 'none': None, }) if default is None: require = True sel = ui.input_options(opts, require=require, default=default) if sel == 'a': return match elif sel == 'm': pass elif sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID
def choose_candidate(candidates, singleton, rec, cur_artist=None, cur_album=None, item=None, itemcount=None): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). Candidates are either AlbumMatch or TrackMatch objects depending on `singleton`. for albums, `cur_artist`, `cur_album`, and `itemcount` must be provided. For singletons, `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate (an AlbumMatch/TrackMatch object). """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: if singleton: print_("No matching recordings found.") opts = ("Use as-is", "Skip", "Enter search", "enter Id", "aBort") else: print_("No matching release found for {0} tracks.".format(itemcount)) print_("For help, see: " "http://beets.readthedocs.org/en/latest/faq.html#nomatch") opts = ("Use as-is", "as Tracks", "Group albums", "Skip", "Enter search", "enter Id", "aBort") sel = ui.input_options(opts) if sel == "u": return importer.action.ASIS elif sel == "t": assert not singleton return importer.action.TRACKS elif sel == "e": return importer.action.MANUAL elif sel == "s": return importer.action.SKIP elif sel == "b": raise importer.ImportAbort() elif sel == "i": return importer.action.MANUAL_ID elif sel == "g": return importer.action.ALBUMS else: assert False # Is the change good enough? bypass_candidates = False if rec != recommendation.none: match = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. require = rec <= recommendation.low if not bypass_candidates: # Display list of candidates. print_( u'Finding tags for {0} "{1} - {2}".'.format( u"track" if singleton else u"album", item.artist if singleton else cur_artist, item.title if singleton else cur_album, ) ) print_(u"Candidates:") for i, match in enumerate(candidates): # Index, metadata, and distance. line = [ u"{0}.".format(i + 1), u"{0} - {1}".format(match.info.artist, match.info.title if singleton else match.info.album), u"({0})".format(dist_string(match.distance)), ] # Penalties. penalties = penalty_string(match.distance, 3) if penalties: line.append(penalties) # Disambiguation disambig = disambig_string(match.info) if disambig: line.append(ui.colorize("lightgray", "(%s)" % disambig)) print_(" ".join(line)) # Ask the user for a choice. if singleton: opts = ("Skip", "Use as-is", "Enter search", "enter Id", "aBort") else: opts = ("Skip", "Use as-is", "as Tracks", "Group albums", "Enter search", "enter Id", "aBort") sel = ui.input_options(opts, numrange=(1, len(candidates))) if sel == "s": return importer.action.SKIP elif sel == "u": return importer.action.ASIS elif sel == "m": pass elif sel == "e": return importer.action.MANUAL elif sel == "t": assert not singleton return importer.action.TRACKS elif sel == "b": raise importer.ImportAbort() elif sel == "i": return importer.action.MANUAL_ID elif sel == "g": return importer.action.ALBUMS else: # Numerical selection. match = candidates[sel - 1] if sel != 1: # When choosing anything but the first match, # disable the default action. require = True bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, match) else: show_change(cur_artist, cur_album, match) # Exact match => tag automatically if we're not in timid mode. if rec == recommendation.strong and not config["import"]["timid"]: return match # Ask for confirmation. if singleton: opts = ("Apply", "More candidates", "Skip", "Use as-is", "Enter search", "enter Id", "aBort") else: opts = ( "Apply", "More candidates", "Skip", "Use as-is", "as Tracks", "Group albums", "Enter search", "enter Id", "aBort", ) default = config["import"]["default_action"].as_choice({"apply": "a", "skip": "s", "asis": "u", "none": None}) if default is None: require = True sel = ui.input_options(opts, require=require, default=default) if sel == "a": return match elif sel == "g": return importer.action.ALBUMS elif sel == "s": return importer.action.SKIP elif sel == "u": return importer.action.ASIS elif sel == "t": assert not singleton return importer.action.TRACKS elif sel == "e": return importer.action.MANUAL elif sel == "b": raise importer.ImportAbort() elif sel == "i": return importer.action.MANUAL_ID
def play_music(self, lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist, at request insert optional arguments. """ command_str = config['play']['command'].get() if not command_str: command_str = util.open_anything() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() raw = config['play']['raw'].get(bool) warning_treshold = config['play']['warning_treshold'].get(int) if relative_to: relative_to = util.normpath(relative_to) # Add optional arguments to the player command. if opts.args: if ARGS_MARKER in command_str: command_str = command_str.replace(ARGS_MARKER, opts.args) else: command_str = "{} {}".format(command_str, opts.args) # Perform search by album and add folders rather than tracks to # playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] sort = lib.get_default_album_sort() for album in selection: if use_folders: paths.append(album.item_dir()) else: paths.extend(item.path for item in sort.sort(album.items())) item_type = 'album' # Perform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] if relative_to: paths = [relpath(path, relative_to) for path in paths] item_type = 'track' item_type += 's' if len(selection) > 1 else '' if not selection: ui.print_( ui.colorize('text_warning', 'No {0} to play.'.format(item_type))) return # Warn user before playing any huge playlists. if warning_treshold and len(selection) > warning_treshold: ui.print_( ui.colorize( 'text_warning', 'You are about to queue {0} {1}.'.format( len(selection), item_type))) if ui.input_options(('Continue', 'Abort')) == 'a': return ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) if raw: open_args = paths else: open_args = [self._create_tmp_playlist(paths)] self._log.debug('executing command: {} {}', command_str, b' '.join(open_args)) try: util.interactive_open(open_args, command_str) except OSError as exc: raise ui.UserError("Could not play the query: " "{0}".format(exc))
def choose_candidate(candidates, singleton, rec, cur_artist=None, cur_album=None, item=None, itemcount=None, extra_choices=[]): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). Candidates are either AlbumMatch or TrackMatch objects depending on `singleton`. for albums, `cur_artist`, `cur_album`, and `itemcount` must be provided. For singletons, `item` must be provided. `extra_choices` is a list of `PromptChoice`s, containg the choices appended by the plugins after receiving the `before_choose_candidate` event. If not empty, the choices are appended to the prompt presented to the user. Returns one of the following: * the result of the choice, which may be SKIP, ASIS, TRACKS, or MANUAL * a candidate (an AlbumMatch/TrackMatch object) * the short letter of a `PromptChoice` (if the user selected one of the `extra_choices`). """ # Sanity check. assert not singleton assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: print_(u"No matching release found for {0} tracks.".format(itemcount)) print_(u'For help, see: ' u'http://beets.readthedocs.org/en/latest/faq.html#nomatch') opts = (u'Skip', u'Enter search', u'enter Id', u'aBort') sel = ui.input_options(opts) if sel == u'e': return importer.action.MANUAL elif sel == u's': return importer.action.SKIP elif sel == u'b': raise importer.ImportAbort() elif sel == u'i': return importer.action.MANUAL_ID else: assert False while True: # Display and choose from candidates. require = rec <= Recommendation.low # Display list of candidates. print_(u'Finding tags for {0} "{1} - {2}".'.format( u'album', cur_artist, cur_album, )) print_(u'Candidates:') for i, match in enumerate(candidates): # Index, metadata, and distance. line = [ u'{0}.'.format(i + 1), u'{0} - {1}'.format( match.info.artist, match.info.album, ), u'({0})'.format(dist_string(match.distance)), ] # Penalties. penalties = penalty_string(match.distance, 3) if penalties: line.append(penalties) # Disambiguation disambig = disambig_string(match.info) if disambig: line.append( ui.colorize('text_highlight_minor', u'(%s)' % disambig)) print_(u' '.join(line)) # Ask the user for a choice. opts = (u'Skip', u'Enter search', u'enter Id', u'aBort') sel = ui.input_options(opts, numrange=(1, len(candidates))) if sel == u's': return importer.action.SKIP elif sel == u'e': return importer.action.MANUAL elif sel == u'b': raise importer.ImportAbort() elif sel == u'i': return importer.action.MANUAL_ID else: # Numerical selection. match = candidates[sel - 1] if sel != 1: # When choosing anything but the first match, # disable the default action. require = True # Show what we're about to do. show_change(cur_artist, cur_album, match) # Exact match => tag automatically. if rec == Recommendation.strong: return match # Ask for confirmation. opts = (u'Apply', u'More candidates', u'Skip', u'Enter search', u'enter Id', u'aBort') default = config['import']['default_action'].as_choice({ u'apply': u'a', u'skip': u's', u'none': None, }) if default is None: require = True sel = ui.input_options(opts, require=require, default=default) if sel == u'a': return match elif sel == u's': return importer.action.SKIP elif sel == u'e': return importer.action.MANUAL elif sel == u'b': raise importer.ImportAbort() elif sel == u'i': return importer.action.MANUAL_ID
def play_music(self, lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist, at request insert optional arguments. """ command_str = config['play']['command'].get() if not command_str: command_str = util.open_anything() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() raw = config['play']['raw'].get(bool) warning_threshold = config['play']['warning_threshold'].get(int) # We use -2 as a default value for warning_threshold to detect if it is # set or not. We can't use a falsey value because it would have an # actual meaning in the configuration of this plugin, and we do not use # -1 because some people might use it as a value to obtain no warning, # which wouldn't be that bad of a practice. if warning_threshold == -2: # if warning_threshold has not been set by user, look for # warning_treshold, to preserve backwards compatibility. See #1803. # warning_treshold has the correct default value of 100. warning_threshold = config['play']['warning_treshold'].get(int) if relative_to: relative_to = util.normpath(relative_to) # Add optional arguments to the player command. if opts.args: if ARGS_MARKER in command_str: command_str = command_str.replace(ARGS_MARKER, opts.args) else: command_str = u"{} {}".format(command_str, opts.args) # Perform search by album and add folders rather than tracks to # playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] sort = lib.get_default_album_sort() for album in selection: if use_folders: paths.append(album.item_dir()) else: paths.extend(item.path for item in sort.sort(album.items())) item_type = 'album' # Perform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] if relative_to: paths = [relpath(path, relative_to) for path in paths] item_type = 'track' item_type += 's' if len(selection) > 1 else '' if not selection: ui.print_(ui.colorize('text_warning', u'No {0} to play.'.format(item_type))) return # Warn user before playing any huge playlists. if warning_threshold and len(selection) > warning_threshold: ui.print_(ui.colorize( 'text_warning', u'You are about to queue {0} {1}.'.format( len(selection), item_type))) if ui.input_options((u'Continue', u'Abort')) == 'a': return ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) if raw: open_args = paths else: open_args = [self._create_tmp_playlist(paths)] self._log.debug(u'executing command: {} {!r}', command_str, open_args) try: util.interactive_open(open_args, command_str) except OSError as exc: raise ui.UserError( "Could not play the query: {0}".format(exc))
def choose_candidate(candidates, singleton, rec, cur_artist=None, cur_album=None, item=None, itemcount=None): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). Candidates are either AlbumMatch or TrackMatch objects depending on `singleton`. for albums, `cur_artist`, `cur_album`, and `itemcount` must be provided. For singletons, `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate (an AlbumMatch/TrackMatch object). """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: if singleton: print_("No matching recordings found.") opts = ('Use as-is', 'Skip', 'Enter search', 'enter Id', 'aBort') else: print_("No matching release found for {0} tracks." .format(itemcount)) print_('For help, see: ' 'https://github.com/sampsyo/beets/wiki/FAQ#wiki-nomatch') opts = ('Use as-is', 'as Tracks', 'Skip', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts) if sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 's': return importer.action.SKIP elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: assert False # Is the change good enough? bypass_candidates = False if rec != recommendation.none: match = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. require = rec <= recommendation.low if not bypass_candidates: # Display list of candidates. if singleton: print_('Finding tags for track "%s - %s".' % (item.artist, item.title)) print_('Candidates:') for i, match in enumerate(candidates): print_('%i. %s - %s (%s)' % (i + 1, match.info.artist, match.info.title, dist_string(match.distance))) else: print_('Finding tags for album "%s - %s".' % (cur_artist, cur_album)) print_('Candidates:') for i, match in enumerate(candidates): line = '%i. %s - %s' % (i + 1, match.info.artist, match.info.album) # Label, year and media disambiguation, if available. disambig = [] if match.info.label: disambig.append(match.info.label) if match.info.year: disambig.append(unicode(match.info.year)) if match.info.media: if match.info.mediums > 1: disambig.append(u'{0}x{1}'.format( match.info.mediums, match.info.media)) else: disambig.append(match.info.media) if match.info.albumdisambig: disambig.append(match.info.albumdisambig) if disambig: line += u' [{0}]'.format(u', '.join(disambig)) line += ' (%s)' % dist_string(match.distance) # Point out the partial matches. if match.extra_items or match.extra_tracks: warning = PARTIAL_MATCH_MESSAGE warning = ui.colorize('yellow', warning) line += u' %s' % warning print_(line) # Ask the user for a choice. if singleton: opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, numrange=(1, len(candidates))) if sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 'e': return importer.action.MANUAL elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: # Numerical selection. match = candidates[sel - 1] if sel != 1: # When choosing anything but the first match, # disable the default action. require = True bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, match) else: show_change(cur_artist, cur_album, match) # Exact match => tag automatically if we're not in timid mode. if rec == recommendation.strong and not config['import']['timid']: return match # Ask for confirmation. if singleton: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') default = config['import']['default_action'].as_choice({ 'apply': 'a', 'skip': 's', 'asis': 'u', 'none': None, }) if default is None: require = True sel = ui.input_options(opts, require=require, default=default) if sel == 'a': return match elif sel == 'm': pass elif sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID
def choose_candidate(candidates, singleton, rec, color, timid, cur_artist=None, cur_album=None, item=None): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). For albums, the candidates are `(dist, items, info)` triples and `cur_artist` and `cur_album` must be provided. For singletons, the candidates are `(dist, info)` pairs and `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate. For albums, a candidate is a `(info, items)` pair; for items, it is just a TrackInfo object. """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: print_("No match found.") if singleton: opts = ('Use as-is', 'Skip', 'Enter search', 'enter Id', 'aBort') else: opts = ('Use as-is', 'as Tracks', 'Skip', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, color=color) if sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 's': return importer.action.SKIP elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: assert False # Is the change good enough? bypass_candidates = False if rec != autotag.RECOMMEND_NONE: if singleton: dist, info = candidates[0] else: dist, items, info = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. if not bypass_candidates: # Display list of candidates. if singleton: print_('Finding tags for track "%s - %s".' % (item.artist, item.title)) print_('Candidates:') for i, (dist, info) in enumerate(candidates): print_('%i. %s - %s (%s)' % (i + 1, info.artist, info.title, dist_string(dist, color))) else: print_('Finding tags for album "%s - %s".' % (cur_artist, cur_album)) print_('Candidates:') for i, (dist, items, info) in enumerate(candidates): line = '%i. %s - %s' % (i + 1, info.artist, info.album) # Label and year disambiguation, if available. label, year = None, None if info.label: label = info.label if info.year: year = unicode(info.year) if label and year: line += u' [%s, %s]' % (label, year) elif label: line += u' [%s]' % label elif year: line += u' [%s]' % year line += ' (%s)' % dist_string(dist, color) # Point out the partial matches. if None in items: warning = PARTIAL_MATCH_MESSAGE if color: warning = ui.colorize('yellow', warning) line += u' %s' % warning print_(line) # Ask the user for a choice. if singleton: opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, numrange=(1, len(candidates)), color=color) if sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 'e': return importer.action.MANUAL elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID else: # Numerical selection. if singleton: dist, info = candidates[sel - 1] else: dist, items, info = candidates[sel - 1] bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, info, dist, color) else: show_change(cur_artist, cur_album, items, info, dist, color) # Exact match => tag automatically if we're not in timid mode. if rec == autotag.RECOMMEND_STRONG and not timid: if singleton: return info else: return info, items # Ask for confirmation. if singleton: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'Enter search', 'enter Id', 'aBort') else: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', 'as Tracks', 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts, color=color) if sel == 'a': if singleton: return info else: return info, items elif sel == 'm': pass elif sel == 's': return importer.action.SKIP elif sel == 'u': return importer.action.ASIS elif sel == 't': assert not singleton return importer.action.TRACKS elif sel == 'e': return importer.action.MANUAL elif sel == 'b': raise importer.ImportAbort() elif sel == 'i': return importer.action.MANUAL_ID
def play_music(lib, opts, args): """Execute query, create temporary playlist and execute player command passing that playlist. """ command_str = config["play"]["command"].get() use_folders = config["play"]["use_folders"].get(bool) sys_name = sys.platform if command_str: command = shlex.split(command_str) else: # If a command isn't set, then let the OS decide how to open the # playlist. if sys_name == "darwin": command = ["open"] elif sys_name == "windows": command = ["start"] elif sys_name == "cygwin": ui.print_( "Must add command in config file, see documentation at http://beets.readthedocs.org/en/v" + __version__ + "/reference/config.html" ) return else: # If not Mac or Windows, then assume Unix. command = ["xdg-open"] # Preform search by album and add folders rather then tracks to playlist. if opts.album: selection = lib.albums(ui.decargs(args)) paths = [] for album in selection: if use_folders: paths.append(album.item_dir()) else: # TODO use core's sorting functionality paths.extend([item.path for item in sorted(album.items(), key=lambda item: (item.disc, item.track))]) item_type = "album" # Preform item query and add tracks to playlist. else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] item_type = "track" item_type += "s" if len(selection) > 1 else "" if not selection: ui.print_(ui.colorize("yellow", "No {0} to play.".format(item_type))) return # Warn user before playing any huge playlists. if len(selection) > 100: ui.print_(ui.colorize("yellow", "You are about to queue {0} {1}.".format(len(selection), item_type))) if ui.input_options(("Continue", "Abort")) == "a": return # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile("w", suffix=".m3u", delete=False) if sys_name == "cygwin": # Needs to use cygpath to get correct patg in file for item in paths: path = subprocess.check_output(["cygpath", "-w", item]) m3u.write(path) command.append(subprocess.check_output(["cygpath", "-w", m3u.name]).strip()) else: for item in paths: m3u.write(item) command.append(m3u.name) m3u.close() # Invoke the command and log the output. output = util.command_output(command) if output: log.debug(u"Output of {0}: {1}".format(command[0], output)) ui.print_(u"Playing {0} {1}.".format(len(paths), item_type))
def edit_objects(self, objs, fields): """Dump a set of Model objects to a file as text, ask the user to edit it, and apply any changes to the objects. Return a boolean indicating whether the edit succeeded. """ # Get the content to edit as raw data structures. old_data = [flatten(o, fields) for o in objs] # Set up a temporary file with the initial data for editing. new = NamedTemporaryFile(suffix=".yaml", delete=False) old_str = dump(old_data) new.write(old_str) new.close() # Loop until we have parseable data and the user confirms. try: while True: # Ask the user to edit the data. edit(new.name) # Read the data back after editing and check whether anything # changed. with open(new.name) as f: new_str = f.read() if new_str == old_str: ui.print_("No changes; aborting.") return False # Parse the updated data. try: new_data = load(new_str) except ParseError as e: ui.print_("Could not read data: {}".format(e)) if ui.input_yn("Edit again to fix? (Y/n)", True): continue else: return False # Show the changes. self.apply_data(objs, old_data, new_data) changed = False for obj in objs: changed |= ui.show_model_changes(obj) if not changed: ui.print_("No changes to apply.") return False # Confirm the changes. choice = ui.input_options(("continue Editing", "apply", "cancel")) if choice == "a": # Apply. return True elif choice == "c": # Cancel. return False elif choice == "e": # Keep editing. # Reset the temporary changes to the objects. for obj in objs: obj.read() continue # Remove the temporary file before returning. finally: os.remove(new.name)
def choose_candidate( candidates, singleton, rec, color, timid, cur_artist=None, cur_album=None, item=None, itemcount=None, per_disc_numbering=False, ): """Given a sorted list of candidates, ask the user for a selection of which candidate to use. Applies to both full albums and singletons (tracks). Candidates are either AlbumMatch or TrackMatch objects depending on `singleton`. for albums, `cur_artist`, `cur_album`, and `itemcount` must be provided. For singletons, `item` must be provided. Returns the result of the choice, which may SKIP, ASIS, TRACKS, or MANUAL or a candidate (an AlbumMatch/TrackMatch object). """ # Sanity check. if singleton: assert item is not None else: assert cur_artist is not None assert cur_album is not None # Zero candidates. if not candidates: if singleton: print_("No matching recordings found.") opts = ("Use as-is", "Skip", "Enter search", "enter Id", "aBort") else: print_("No matching release found for {0} tracks.".format(itemcount)) print_("For help, see: " "https://github.com/sampsyo/beets/wiki/FAQ#wiki-nomatch") opts = ("Use as-is", "as Tracks", "Skip", "Enter search", "enter Id", "aBort") sel = ui.input_options(opts, color=color) if sel == "u": return importer.action.ASIS elif sel == "t": assert not singleton return importer.action.TRACKS elif sel == "e": return importer.action.MANUAL elif sel == "s": return importer.action.SKIP elif sel == "b": raise importer.ImportAbort() elif sel == "i": return importer.action.MANUAL_ID else: assert False # Is the change good enough? bypass_candidates = False if rec != autotag.RECOMMEND_NONE: match = candidates[0] bypass_candidates = True while True: # Display and choose from candidates. if not bypass_candidates: # Display list of candidates. if singleton: print_('Finding tags for track "%s - %s".' % (item.artist, item.title)) print_("Candidates:") for i, match in enumerate(candidates): print_( "%i. %s - %s (%s)" % (i + 1, match.info.artist, match.info.title, dist_string(match.distance, color)) ) else: print_('Finding tags for album "%s - %s".' % (cur_artist, cur_album)) print_("Candidates:") for i, match in enumerate(candidates): line = "%i. %s - %s" % (i + 1, match.info.artist, match.info.album) # Label and year disambiguation, if available. label, year = None, None if match.info.label: label = match.info.label if match.info.year: year = unicode(match.info.year) if label and year: line += u" [%s, %s]" % (label, year) elif label: line += u" [%s]" % label elif year: line += u" [%s]" % year line += " (%s)" % dist_string(match.distance, color) # Point out the partial matches. if match.extra_items or match.extra_tracks: warning = PARTIAL_MATCH_MESSAGE if color: warning = ui.colorize("yellow", warning) line += u" %s" % warning print_(line) # Ask the user for a choice. if singleton: opts = ("Skip", "Use as-is", "Enter search", "enter Id", "aBort") else: opts = ("Skip", "Use as-is", "as Tracks", "Enter search", "enter Id", "aBort") sel = ui.input_options(opts, numrange=(1, len(candidates)), color=color) if sel == "s": return importer.action.SKIP elif sel == "u": return importer.action.ASIS elif sel == "e": return importer.action.MANUAL elif sel == "t": assert not singleton return importer.action.TRACKS elif sel == "b": raise importer.ImportAbort() elif sel == "i": return importer.action.MANUAL_ID else: # Numerical selection. if singleton: match = candidates[sel - 1] else: match = candidates[sel - 1] bypass_candidates = False # Show what we're about to do. if singleton: show_item_change(item, match, color) else: show_change(cur_artist, cur_album, match, color, per_disc_numbering) # Exact match => tag automatically if we're not in timid mode. if rec == autotag.RECOMMEND_STRONG and not timid: return match # Ask for confirmation. if singleton: opts = ("Apply", "More candidates", "Skip", "Use as-is", "Enter search", "enter Id", "aBort") else: opts = ("Apply", "More candidates", "Skip", "Use as-is", "as Tracks", "Enter search", "enter Id", "aBort") sel = ui.input_options(opts, color=color) if sel == "a": return match elif sel == "m": pass elif sel == "s": return importer.action.SKIP elif sel == "u": return importer.action.ASIS elif sel == "t": assert not singleton return importer.action.TRACKS elif sel == "e": return importer.action.MANUAL elif sel == "b": raise importer.ImportAbort() elif sel == "i": return importer.action.MANUAL_ID