Example #1
0
    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
Example #2
0
    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
Example #3
0
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))
Example #4
0
    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
Example #5
0
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
Example #6
0
File: play.py Project: 241n/beets
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))
Example #7
0
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
Example #8
0
    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]
Example #9
0
    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]
Example #10
0
    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)
Example #11
0
    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)
Example #12
0
    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
Example #13
0
    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))
Example #14
0
    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
Example #15
0
 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
Example #16
0
    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
Example #17
0
    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)
Example #18
0
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()
Example #19
0
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
Example #20
0
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
Example #21
0
    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)
Example #22
0
    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)
Example #23
0
    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))
Example #24
0
File: play.py Project: Ifiht/beets
    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])
Example #25
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)
Example #26
0
    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))
Example #28
0
    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)
Example #29
0
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()
Example #30
0
    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])
Example #31
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)
Example #32
0
 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")
Example #33
0
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
Example #34
0
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
Example #35
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))
Example #36
0
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
Example #37
0
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
Example #38
0
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
Example #39
0
    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))
Example #40
0
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
Example #41
0
    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))
Example #42
0
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
Example #43
0
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
Example #44
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)
    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))
Example #45
0
    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)
Example #46
0
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