Example #1
0
def show_change(cur_artist, cur_album, items, info, dist, color=True):
    """Print out a representation of the changes that will be made if
    tags are changed from (cur_artist, cur_album, items) to info with
    distance dist.
    """
    def show_album(artist, album):
        if artist:
            print_('    %s - %s' % (artist, album))
        elif album:
            print_('    %s' % album)
        else:
            print_('    (unknown album)')

    # Identify the album in question.
    if cur_artist != info['artist'] or \
            (cur_album != info['album'] and info['album'] != VARIOUS_ARTISTS):
        artist_l, artist_r = cur_artist or '', info['artist']
        album_l,  album_r  = cur_album  or '', info['album']
        if artist_r == VARIOUS_ARTISTS:
            # Hide artists for VA releases.
            artist_l, artist_r = u'', u''

        if color:
            artist_l, artist_r = ui.colordiff(artist_l, artist_r)
            album_l, album_r   = ui.colordiff(album_l, album_r)

        print_("Correcting tags from:")
        show_album(artist_l, album_l)
        print_("To:")
        show_album(artist_r, album_r)
    else:
        print_("Tagging: %s - %s" % (info['artist'], info['album']))

    # Distance/similarity.
    print_('(Similarity: %s)' % dist_string(dist, color))

    # Tracks.
    for i, (item, track_data) in enumerate(zip(items, info['tracks'])):
        cur_track = str(item.track)
        new_track = str(i+1)
        cur_title = item.title
        new_title = track_data['title']
        
        # Possibly colorize changes.
        if color:
            cur_title, new_title = ui.colordiff(cur_title, new_title)
            if cur_track != new_track:
                cur_track = ui.colorize('red', cur_track)
                new_track = ui.colorize('red', new_track)
        
        if cur_title != new_title and cur_track != new_track:
            print_(" * %s (%s) -> %s (%s)" % (
                cur_title, cur_track, new_title, new_track
            ))
        elif cur_title != new_title:
            print_(" * %s -> %s" % (cur_title, new_title))
        elif cur_track != new_track:
            print_(" * %s (%s -> %s)" % (item.title, cur_track, new_track))
Example #2
0
def dist_string(dist, color):
    """Formats a distance (a float) as a similarity percentage string.
    The string is colorized if color is True.
    """
    out = '%.1f%%' % ((1 - dist) * 100)
    if color:
        if dist <= autotag.STRONG_REC_THRESH:
            out = ui.colorize('green', out)
        elif dist <= autotag.MEDIUM_REC_THRESH:
            out = ui.colorize('yellow', out)
        else:
            out = ui.colorize('red', out)
    return out
Example #3
0
    def show_album(artist, album, partial=False):
        if artist:
            album_description = u'    %s - %s' % (artist, album)
        elif album:
            album_description = u'    %s' % album
        else:
            album_description = u'    (unknown album)'

        # Add a suffix if this is a partial match.
        if partial:
            warning = PARTIAL_MATCH_MESSAGE
        else:
            warning = None
        if color and warning:
            warning = ui.colorize('yellow', warning)

        out = album_description
        if warning:
            out += u' ' + warning
        print_(out)
Example #4
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 #5
0
def show_change(cur_artist, cur_album, items, info, dist, color=True):
    """Print out a representation of the changes that will be made if
    tags are changed from (cur_artist, cur_album, items) to info with
    distance dist.
    """
    def show_album(artist, album, partial=False):
        if artist:
            album_description = u'    %s - %s' % (artist, album)
        elif album:
            album_description = u'    %s' % album
        else:
            album_description = u'    (unknown album)'

        # Add a suffix if this is a partial match.
        if partial:
            warning = PARTIAL_MATCH_MESSAGE
        else:
            warning = None
        if color and warning:
            warning = ui.colorize('yellow', warning)

        out = album_description
        if warning:
            out += u' ' + warning
        print_(out)

    # Record if the match is partial or not.
    partial_match = None in items

    # Identify the album in question.
    if cur_artist != info.artist or \
            (cur_album != info.album and info.album != VARIOUS_ARTISTS):
        artist_l, artist_r = cur_artist or '', info.artist
        album_l,  album_r  = cur_album  or '', info.album
        if artist_r == VARIOUS_ARTISTS:
            # Hide artists for VA releases.
            artist_l, artist_r = u'', u''

        if color:
            artist_l, artist_r = ui.colordiff(artist_l, artist_r)
            album_l, album_r   = ui.colordiff(album_l, album_r)

        print_("Correcting tags from:")
        show_album(artist_l, album_l)
        print_("To:")
        show_album(artist_r, album_r)
    else:
        message = u"Tagging: %s - %s" % (info.artist, info.album)
        if partial_match:
            warning = PARTIAL_MATCH_MESSAGE
            if color:
                warning = ui.colorize('yellow', PARTIAL_MATCH_MESSAGE)
            message += u' ' + warning
        print_(message)

    # Distance/similarity.
    print_('(Similarity: %s)' % dist_string(dist, color))

    # Tracks.
    missing_tracks = []
    for i, (item, track_info) in enumerate(zip(items, info.tracks)):
        if not item:
            missing_tracks.append((i, track_info))
            continue

        # Get displayable LHS and RHS values.
        cur_track = unicode(item.track)
        new_track = unicode(i+1)
        cur_title = item.title
        new_title = track_info.title
        if item.length and track_info.length:
            cur_length = ui.human_seconds_short(item.length)
            new_length = ui.human_seconds_short(track_info.length)
            if color:
                cur_length = ui.colorize('red', cur_length)
                new_length = ui.colorize('red', new_length)
        
        # Possibly colorize changes.
        if color:
            cur_title, new_title = ui.colordiff(cur_title, new_title)
            if cur_track != new_track:
                cur_track = ui.colorize('red', cur_track)
                new_track = ui.colorize('red', new_track)

        # Show filename (non-colorized) when title is not set.
        if not item.title.strip():
            cur_title = displayable_path(os.path.basename(item.path))
        
        if cur_title != new_title:
            lhs, rhs = cur_title, new_title
            if cur_track != new_track:
                lhs += u' (%s)' % cur_track
                rhs += u' (%s)' % new_track
            print_(u" * %s -> %s" % (lhs, rhs))
        else:
            line = u' * %s' % item.title
            display = False
            if cur_track != new_track:
                display = True
                line += u' (%s -> %s)' % (cur_track, new_track)
            if item.length and track_info.length and \
                    abs(item.length - track_info.length) > 2.0:
                display = True
                line += u' (%s -> %s)' % (cur_length, new_length)
            if display:
                print_(line)
    for i, track_info in missing_tracks:
        line = u' * Missing track: %s (%d)' % (track_info.title, i+1)
        if color:
            line = ui.colorize('yellow', line)
        print_(line)
Example #6
0
def show_change(cur_artist, cur_album, items, info, dist, color=True):
    """Print out a representation of the changes that will be made if
    tags are changed from (cur_artist, cur_album, items) to info with
    distance dist.
    """
    def show_album(artist, album):
        if artist:
            print_('    %s - %s' % (artist, album))
        elif album:
            print_('    %s' % album)
        else:
            print_('    (unknown album)')

    # Identify the album in question.
    if cur_artist != info.artist or \
            (cur_album != info.album and info.album != VARIOUS_ARTISTS):
        artist_l, artist_r = cur_artist or '', info.artist
        album_l,  album_r  = cur_album  or '', info.album
        if artist_r == VARIOUS_ARTISTS:
            # Hide artists for VA releases.
            artist_l, artist_r = u'', u''

        if color:
            artist_l, artist_r = ui.colordiff(artist_l, artist_r)
            album_l, album_r   = ui.colordiff(album_l, album_r)

        print_("Correcting tags from:")
        show_album(artist_l, album_l)
        print_("To:")
        show_album(artist_r, album_r)
    else:
        print_("Tagging: %s - %s" % (info.artist, info.album))

    # Distance/similarity.
    print_('(Similarity: %s)' % dist_string(dist, color))

    # Tracks.
    for i, (item, track_info) in enumerate(zip(items, info.tracks)):
        cur_track = str(item.track)
        new_track = str(i+1)
        cur_title = item.title
        new_title = track_info.title
        
        # Possibly colorize changes.
        if color:
            cur_title, new_title = ui.colordiff(cur_title, new_title)
            if cur_track != new_track:
                cur_track = ui.colorize('red', cur_track)
                new_track = ui.colorize('red', new_track)

        # Show filename (non-colorized) when title is not set.
        if not item.title.strip():
            cur_title = os.path.basename(item.path)
        
        if cur_title != new_title and cur_track != new_track:
            print_(" * %s (%s) -> %s (%s)" % (
                cur_title, cur_track, new_title, new_track
            ))
        elif cur_title != new_title:
            print_(" * %s -> %s" % (cur_title, new_title))
        elif cur_track != new_track:
            print_(" * %s (%s -> %s)" % (item.title, cur_track, new_track))
Example #7
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 #8
0
def show_change(cur_artist, cur_album, match, color=True,
                per_disc_numbering=False):
    """Print out a representation of the changes that will be made if an
    album's tags are changed according to `match`, which must be an AlbumMatch
    object.
    """
    def show_album(artist, album, partial=False):
        if artist:
            album_description = u'    %s - %s' % (artist, album)
        elif album:
            album_description = u'    %s' % album
        else:
            album_description = u'    (unknown album)'

        # Add a suffix if this is a partial match.
        if partial:
            warning = PARTIAL_MATCH_MESSAGE
        else:
            warning = None
        if color and warning:
            warning = ui.colorize('yellow', warning)

        out = album_description
        if warning:
            out += u' ' + warning
        print_(out)

    def format_index(track_info):
        """Return a string representing the track index of the given
        TrackInfo object.
        """
        if per_disc_numbering:
            if match.info.mediums > 1:
                return u'{0}-{1}'.format(track_info.medium,
                                         track_info.medium_index)
            else:
                return unicode(track_info.medium_index)
        else:
            return unicode(track_info.index)

    # Identify the album in question.
    if cur_artist != match.info.artist or \
            (cur_album != match.info.album and
             match.info.album != VARIOUS_ARTISTS):
        artist_l, artist_r = cur_artist or '', match.info.artist
        album_l,  album_r  = cur_album  or '', match.info.album
        if artist_r == VARIOUS_ARTISTS:
            # Hide artists for VA releases.
            artist_l, artist_r = u'', u''

        if color:
            artist_l, artist_r = ui.colordiff(artist_l, artist_r)
            album_l, album_r   = ui.colordiff(album_l, album_r)

        print_("Correcting tags from:")
        show_album(artist_l, album_l)
        print_("To:")
        show_album(artist_r, album_r)
    else:
        message = u"Tagging: %s - %s" % (match.info.artist, match.info.album)
        if match.extra_items or match.extra_tracks:
            warning = PARTIAL_MATCH_MESSAGE
            if color:
                warning = ui.colorize('yellow', PARTIAL_MATCH_MESSAGE)
            message += u' ' + warning
        print_(message)

    # Distance/similarity.
    print_('(Similarity: %s)' % dist_string(match.distance, color))

    # Tracks.
    pairs = match.mapping.items()
    pairs.sort(key=lambda (_, track_info): track_info.index)
    for item, track_info in pairs:
        # Get displayable LHS and RHS values.
        cur_track = unicode(item.track)
        new_track = format_index(track_info)
        tracks_differ = item.track not in (track_info.index,
                                           track_info.medium_index)
        cur_title = item.title
        new_title = track_info.title
        if item.length and track_info.length:
            cur_length = ui.human_seconds_short(item.length)
            new_length = ui.human_seconds_short(track_info.length)
            if color:
                cur_length = ui.colorize('red', cur_length)
                new_length = ui.colorize('red', new_length)

        # Possibly colorize changes.
        if color:
            cur_title, new_title = ui.colordiff(cur_title, new_title)
            cur_track = ui.colorize('red', cur_track)
            new_track = ui.colorize('red', new_track)

        # Show filename (non-colorized) when title is not set.
        if not item.title.strip():
            cur_title = displayable_path(os.path.basename(item.path))

        if cur_title != new_title:
            lhs, rhs = cur_title, new_title
            if tracks_differ:
                lhs += u' (%s)' % cur_track
                rhs += u' (%s)' % new_track
            print_(u" * %s -> %s" % (lhs, rhs))
        else:
            line = u' * %s' % item.title
            display = False
            if tracks_differ:
                display = True
                line += u' (%s -> %s)' % (cur_track, new_track)
            if item.length and track_info.length and \
                    abs(item.length - track_info.length) > 2.0:
                display = True
                line += u' (%s vs. %s)' % (cur_length, new_length)
            if display:
                print_(line)

    # Missing and unmatched tracks.
    for track_info in match.extra_tracks:
        line = u' * Missing track: {0} ({1})'.format(track_info.title,
                                                     format_index(track_info))
        if color:
            line = ui.colorize('yellow', line)
        print_(line)
    for item in match.extra_items:
        line = u' * Unmatched track: {0} ({1})'.format(item.title, item.track)
        if color:
            line = ui.colorize('yellow', line)
        print_(line)
Example #9
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 #10
0
def show_change(cur_artist,
                cur_album,
                match,
                color=True,
                per_disc_numbering=False):
    """Print out a representation of the changes that will be made if an
    album's tags are changed according to `match`, which must be an AlbumMatch
    object.
    """
    def show_album(artist, album, partial=False):
        if artist:
            album_description = u'    %s - %s' % (artist, album)
        elif album:
            album_description = u'    %s' % album
        else:
            album_description = u'    (unknown album)'

        # Add a suffix if this is a partial match.
        if partial:
            warning = PARTIAL_MATCH_MESSAGE
        else:
            warning = None
        if color and warning:
            warning = ui.colorize('yellow', warning)

        out = album_description
        if warning:
            out += u' ' + warning
        print_(out)

    def format_index(track_info):
        """Return a string representing the track index of the given
        TrackInfo object.
        """
        if per_disc_numbering:
            if match.info.mediums > 1:
                return u'{0}-{1}'.format(track_info.medium,
                                         track_info.medium_index)
            else:
                return unicode(track_info.medium_index)
        else:
            return unicode(track_info.index)

    # Identify the album in question.
    if cur_artist != match.info.artist or \
            (cur_album != match.info.album and
             match.info.album != VARIOUS_ARTISTS):
        artist_l, artist_r = cur_artist or '', match.info.artist
        album_l, album_r = cur_album or '', match.info.album
        if artist_r == VARIOUS_ARTISTS:
            # Hide artists for VA releases.
            artist_l, artist_r = u'', u''

        if color:
            artist_l, artist_r = ui.colordiff(artist_l, artist_r)
            album_l, album_r = ui.colordiff(album_l, album_r)

        print_("Correcting tags from:")
        show_album(artist_l, album_l)
        print_("To:")
        show_album(artist_r, album_r)
    else:
        message = u"Tagging: %s - %s" % (match.info.artist, match.info.album)
        if match.extra_items or match.extra_tracks:
            warning = PARTIAL_MATCH_MESSAGE
            if color:
                warning = ui.colorize('yellow', PARTIAL_MATCH_MESSAGE)
            message += u' ' + warning
        print_(message)

    # Distance/similarity.
    print_('(Similarity: %s)' % dist_string(match.distance, color))

    # Tracks.
    pairs = match.mapping.items()
    pairs.sort(key=lambda (_, track_info): track_info.index)
    for item, track_info in pairs:
        # Get displayable LHS and RHS values.
        cur_track = unicode(item.track)
        new_track = format_index(track_info)
        tracks_differ = item.track not in (track_info.index,
                                           track_info.medium_index)
        cur_title = item.title
        new_title = track_info.title
        if item.length and track_info.length:
            cur_length = ui.human_seconds_short(item.length)
            new_length = ui.human_seconds_short(track_info.length)
            if color:
                cur_length = ui.colorize('red', cur_length)
                new_length = ui.colorize('red', new_length)

        # Possibly colorize changes.
        if color:
            cur_title, new_title = ui.colordiff(cur_title, new_title)
            cur_track = ui.colorize('red', cur_track)
            new_track = ui.colorize('red', new_track)

        # Show filename (non-colorized) when title is not set.
        if not item.title.strip():
            cur_title = displayable_path(os.path.basename(item.path))

        if cur_title != new_title:
            lhs, rhs = cur_title, new_title
            if tracks_differ:
                lhs += u' (%s)' % cur_track
                rhs += u' (%s)' % new_track
            print_(u" * %s -> %s" % (lhs, rhs))
        else:
            line = u' * %s' % item.title
            display = False
            if tracks_differ:
                display = True
                line += u' (%s -> %s)' % (cur_track, new_track)
            if item.length and track_info.length and \
                    abs(item.length - track_info.length) > 2.0:
                display = True
                line += u' (%s vs. %s)' % (cur_length, new_length)
            if display:
                print_(line)

    # Missing and unmatched tracks.
    for track_info in match.extra_tracks:
        line = u' * Missing track: {0} ({1})'.format(track_info.title,
                                                     format_index(track_info))
        if color:
            line = ui.colorize('yellow', line)
        print_(line)
    for item in match.extra_items:
        line = u' * Unmatched track: {0} ({1})'.format(item.title, item.track)
        if color:
            line = ui.colorize('yellow', line)
        print_(line)