예제 #1
0
def process_album(album_path):
    global msgs
    album, msgs = get_album(album_path)
    if not msgs.errors:
        find_common_album_tags(album)
        find_identical_album_tags(album)
        process_tag_overrides(album)
        check_tag_validity(album)
    if not msgs.errors:
        check_new_path(album)
        album.old_files = OrderedDict()
        album.new_files = {}
        check_and_prepare_audio_files(album)
        check_and_prepare_auxiliary_files(album)
        prepare_other_files(album)
    if msgs:
        kind = ('Errors' if not msgs.warnings
                else 'Warnings' if not msgs.errors
                else 'Errors and warnings')
        print('\n%s found in %s\n%s' % (kind, album_path, msgs))
        if msgs.errors:
            return
    if args.verbose:
        print('\nProcessing %s' % album_path)
    if args.dest:
        do_move_or_copy(album)
    else:
        do_rename_in_place(album)
    return
def process_album(album_path):
    global msgs
    album, msgs = get_album(album_path)
    if not msgs.errors:
        find_common_album_tags(album)
        find_identical_album_tags(album)
        process_tag_overrides(album)
        check_tag_validity(album)
    if not msgs.errors:
        check_new_path(album)
        album.old_files = OrderedDict()
        album.new_files = {}
        check_and_prepare_audio_files(album)
        check_and_prepare_auxiliary_files(album)
        prepare_other_files(album)
    if msgs:
        kind = ('Errors' if not msgs.warnings else
                'Warnings' if not msgs.errors else 'Errors and warnings')
        print('\n%s found in %s\n%s' % (kind, album_path, msgs))
        if msgs.errors:
            return
    if args.verbose:
        print('\nProcessing %s' % album_path)
    if args.dest:
        do_move_or_copy(album)
    else:
        do_rename_in_place(album)
    return
예제 #3
0
def parse_args():
    global args, prog
    parser = argparse.ArgumentParser(description='''
            Rename and copy/move FLAC files and associated files according to
            the tags in those FLAC files.''')
    parser.add_argument('source', help='Root of tree with files to process')
    parser.add_argument('dest', nargs='?',
                        help='Root of tree to which files are moved/copied. '
                             'If omitted, then files and album folders are '
                             'renamed in place as necessary and not '
                             'copied/moved to a new location.')
    parser.add_argument('-l', '--len', type=int, default=default_maxpath, metavar='max',
                        help="Truncate generated pathnames that exceed 'max' "
                             "characters (default %d)" % default_maxpath)
    parser.add_argument('-m', '--move', action='store_true',
                        help='Move files to the destination instead of copying')
    parser.add_argument('-n', '--dry-run', action='store_true',
                        help="Don't actually move/copy/rename, just show what "
                             "would be done (implies -vv)")
    parser.add_argument('-o', '--override', action='append', nargs=2,
                        metavar=('tag', 'value'),
                        help="Override a tag by marking it as identical across "
                             "an album, with the given new value. Useful for "
                             "overriding the values which are used to create "
                             "the path to an album's new location. Separate "
                             "multiple values for a tag with semicolons. "
                             'Example: -o artist "John Doe;Jane Smith"')
    parser.add_argument('-p', '--pause', action='store_true',
                        help='Pause before exiting')
    parser.add_argument('-s', '--sorted-artist', action='store_true',
                        help="For non-classical albums, use the [Album Artist Sort] "
                             "tag, not [AlbumArtist], for the top-level directory "
                             "under which albums are written.")
    parser.add_argument('-t', '--truncate-warn', action='store_false',
                        help='Disable the warning if a file needs to be truncated')
    parser.add_argument('-v', '--verbose', action='count', default=0,
                        help="Output more info about what's being done. Repeated "
                             "uses (-vv) will display even more info.")
    args = parser.parse_args()
    prog = parser.prog
    if not os.path.exists(args.source):
        raise Error('source path does not exist')
    args.source = os.path.abspath(args.source)
    if args.dest:
        args.dest = os.path.abspath(args.dest)
        if args.source.lower() == args.dest.lower():
            raise Error('source and destination arguments must be different')
    if args.dry_run:
        args.verbose = 2
        print('Note: This is a dry run; no changes are being made')
예제 #4
0
def main():
    try:
        parse_args()
        for album_path in find_albums(args.source):
            process_album(album_path)
    except Error as e:
        print('%s: error: %s' % (prog, e))
        exit_code = 1
    except Exception as e:
        print(e)
        exit_code = 2
    else:
        exit_code = 0
    if args.pause:
        try:
            input('\nPress Enter when ready...')
        except:
            pass
    sys.exit(exit_code)
예제 #5
0
def do_move_or_copy(album):
    # Perform the actual move/copy when source and destination are specified.
    operation = 'Move' if args.move else 'Copy'
    if args.verbose > 1:
        print('%s to:   %s' % (operation, album.new_path))
    if not args.dry_run and not os.path.exists(album.new_path):
        os.makedirs(album.new_path)
    for old, new in album.old_files.items():
        if args.verbose > 1:
            print('%s %s\n  -> %s' % (operation, old, new))
        if not args.dry_run:
            old_path = os.path.join(album.path, old)
            new_path = os.path.join(album.new_path, new)
            if args.move:
                shutil.move(old_path, new_path)
            else:
                shutil.copy2(old_path, new_path)
    if args.move:
        remove_empty_directories(album.path, args.source)
def main():
    try:
        parse_args()
        for album_path in find_albums(args.source):
            process_album(album_path)
    except Error as e:
        print('%s: error: %s' % (prog, e))
        exit_code = 1
    except Exception as e:
        print(e)
        exit_code = 2
    else:
        exit_code = 0
    if args.pause:
        try:
            input('\nPress Enter when ready...')
        except:
            pass
    sys.exit(exit_code)
def do_move_or_copy(album):
    # Perform the actual move/copy when source and destination are specified.
    operation = 'Move' if args.move else 'Copy'
    if args.verbose > 1:
        print('%s to:   %s' % (operation, album.new_path))
    if not args.dry_run and not os.path.exists(album.new_path):
        os.makedirs(album.new_path)
    for old, new in album.old_files.items():
        if args.verbose > 1:
            print('%s %s\n  -> %s' % (operation, old, new))
        if not args.dry_run:
            old_path = os.path.join(album.path, old)
            new_path = os.path.join(album.new_path, new)
            if args.move:
                shutil.move(old_path, new_path)
            else:
                shutil.copy2(old_path, new_path)
    if args.move:
        remove_empty_directories(album.path, args.source)
예제 #8
0
def process_album(album_path):
    global msgs, album_count, disc_count, track_count, warn_count
    warn = False
    album_count += 1
    album, msgs = get_album(album_path)
    if album:
        check_disc_numbers(album)
        check_identical_tags_across_discs(album)
        check_nontag_info(album)
    if msgs:
        print("\nEarly checks of '%s' found problems:" % album_path)
        print(msgs)
    for discnum, disc in album.items():
        msgs.clear()
        disc_count += 1
        track_count += len(disc)
        handle_mapped_tags(disc)
        find_common_disc_tags(disc)
        find_identical_disc_tags(disc)
        check_profile(disc)
        check_inaccurate_rips(disc)
        check_missing_tags(disc)
        check_unknown_tags(disc)
        check_multivalued_tags(disc)
        check_track_numbers(disc)
        check_identical_tags(disc)
        check_different_tags(disc)
        check_dups_in_tags(disc)
        check_sort_tags(disc)
        check_leading_the(disc)
        check_multiple_artists(disc)
        check_compilation(disc)
        check_orchestra(disc)
        find_selected_tags(disc)
        if msgs or args.verbose:
            album_display = album_path
            try:
                if int(
                        flatten_tag(
                            next(iter(disc.values())).get("disctotal",
                                                          '1'))) != 1:
                    album_display += ' (Disc %d)' % discnum
            except ValueError:
                pass
            print("\nChecking '%s'" % album_display)
        if msgs:
            print(msgs)
    if msgs.errors or msgs.warnings:
        warn_count += 1
예제 #9
0
def main():
    parse_args()
    for root in sorted(args.path):
        for album_path in find_albums(root):
            process_album(album_path)

    def plural(count, name, zero='0'):
        if count == 1:
            return '1 ' + name
        elif count == 0:
            return '%s %ss' % (zero, name)
        else:
            return '%d %ss' % (count, name)

    print("\nProcessed %s, %s, %s - %s with issues" %
          (plural(album_count, 'album'), plural(disc_count, 'disc'),
           plural(track_count, 'track'), plural(warn_count, 'album', zero='No')))
    if args.pause:
        try:
            input('\nPress Enter when ready...')
        except:
            pass
예제 #10
0
def main():
    parse_args()
    for root in sorted(args.path):
        for album_path in find_albums(root):
            process_album(album_path)

    def plural(count, name, zero='0'):
        if count == 1:
            return '1 ' + name
        elif count == 0:
            return '%s %ss' % (zero, name)
        else:
            return '%d %ss' % (count, name)

    print(
        "\nProcessed %s, %s, %s - %s with issues" %
        (plural(album_count, 'album'), plural(disc_count, 'disc'),
         plural(track_count, 'track'), plural(warn_count, 'album', zero='No')))
    if args.pause:
        try:
            input('\nPress Enter when ready...')
        except:
            pass
예제 #11
0
def process_album(album_path):
    global msgs, album_count, disc_count, track_count, warn_count
    warn = False
    album_count += 1
    album, msgs = get_album(album_path)
    if album:
        check_disc_numbers(album)
        check_identical_tags_across_discs(album)
        check_nontag_info(album)
    if msgs:
        print("\nEarly checks of '%s' found problems:" % album_path)
        print(msgs)
    for discnum, disc in album.items():
        msgs.clear()
        disc_count += 1
        track_count += len(disc)
        handle_mapped_tags(disc)
        find_common_disc_tags(disc)
        find_identical_disc_tags(disc)
        check_profile(disc)
        check_inaccurate_rips(disc)
        check_missing_tags(disc)
        check_unknown_tags(disc)
        check_multivalued_tags(disc)
        check_track_numbers(disc)
        check_identical_tags(disc)
        check_different_tags(disc)
        check_dups_in_tags(disc)
        check_sort_tags(disc)
        check_leading_the(disc)
        check_multiple_artists(disc)
        check_compilation(disc)
        check_orchestra(disc)
        find_selected_tags(disc)
        if msgs or args.verbose:
            album_display = album_path
            try:
                if int(flatten_tag(next(iter(disc.values())).get("disctotal", '1'))) != 1:
                    album_display += ' (Disc %d)' % discnum
            except ValueError:
                pass
            print("\nChecking '%s'" % album_display)
        if msgs:
            print(msgs)
    if msgs.errors or msgs.warnings:
        warn_count += 1
예제 #12
0
def remove_empty_directories(path, root):
    # Remove empty directories starting at path and working through the
    # parent directories, stopping at the root path.
    while not os.listdir(path) and path != root:
        try:
            if args.verbose > 1:
                print('rmdir %s' % path)
            os.rmdir(path)
        except Exception as e:
            print('Warning: Failed to remove empty directory %s' % path)
            print(e)
            return
        path = os.path.dirname(path)
def remove_empty_directories(path, root):
    # Remove empty directories starting at path and working through the
    # parent directories, stopping at the root path.
    while not os.listdir(path) and path != root:
        try:
            if args.verbose > 1:
                print('rmdir %s' % path)
            os.rmdir(path)
        except Exception as e:
            print('Warning: Failed to remove empty directory %s' % path)
            print(e)
            return
        path = os.path.dirname(path)
예제 #14
0
def do_rename_in_place(album):
    # Perform any file renames needed when no destination is specified.
    # Also rename the album folder itself if necessary.
    rename_folder = album.new_folder != os.path.basename(album.path)
    if args.verbose > 1 and rename_folder:
        print('Rename to: %s' % album.new_path)
    found = False
    for old, new in album.old_files.items():
        if old != new:
            found = True
            if args.verbose > 1:
                print('Rename %s\n    -> %s' % (old, new))
            if not args.dry_run:
                old_path = os.path.join(album.path, old)
                new_path = os.path.join(album.path, new)
                os.rename(old_path, new_path)
    if not args.dry_run and rename_folder:
        os.rename(album.path, album.new_path)
    if not found and args.verbose > 1:
        print('No album files renamed')
def do_rename_in_place(album):
    # Perform any file renames needed when no destination is specified.
    # Also rename the album folder itself if necessary.
    rename_folder = album.new_folder != os.path.basename(album.path)
    if args.verbose > 1 and rename_folder:
        print('Rename to: %s' % album.new_path)
    found = False
    for old, new in album.old_files.items():
        if old != new:
            found = True
            if args.verbose > 1:
                print('Rename %s\n    -> %s' % (old, new))
            if not args.dry_run:
                old_path = os.path.join(album.path, old)
                new_path = os.path.join(album.path, new)
                os.rename(old_path, new_path)
    if not args.dry_run and rename_folder:
        os.rename(album.path, album.new_path)
    if not found and args.verbose > 1:
        print('No album files renamed')
def parse_args():
    global args, prog
    parser = argparse.ArgumentParser(description='''
            Rename and copy/move FLAC files and associated files according to
            the tags in those FLAC files.''')
    parser.add_argument('source', help='Root of tree with files to process')
    parser.add_argument('dest',
                        nargs='?',
                        help='Root of tree to which files are moved/copied. '
                        'If omitted, then files and album folders are '
                        'renamed in place as necessary and not '
                        'copied/moved to a new location.')
    parser.add_argument('-l',
                        '--len',
                        type=int,
                        default=default_maxpath,
                        metavar='max',
                        help="Truncate generated pathnames that exceed 'max' "
                        "characters (default %d)" % default_maxpath)
    parser.add_argument(
        '-m',
        '--move',
        action='store_true',
        help='Move files to the destination instead of copying')
    parser.add_argument('-n',
                        '--dry-run',
                        action='store_true',
                        help="Don't actually move/copy/rename, just show what "
                        "would be done (implies -vv)")
    parser.add_argument(
        '-o',
        '--override',
        action='append',
        nargs=2,
        metavar=('tag', 'value'),
        help="Override a tag by marking it as identical across "
        "an album, with the given new value. Useful for "
        "overriding the values which are used to create "
        "the path to an album's new location. Separate "
        "multiple values for a tag with semicolons. "
        'Example: -o artist "John Doe;Jane Smith"')
    parser.add_argument('-p',
                        '--pause',
                        action='store_true',
                        help='Pause before exiting')
    parser.add_argument(
        '-s',
        '--sorted-artist',
        action='store_true',
        help="For non-classical albums, use the [Album Artist Sort] "
        "tag, not [AlbumArtist], for the top-level directory "
        "under which albums are written.")
    parser.add_argument(
        '-t',
        '--truncate-warn',
        action='store_false',
        help='Disable the warning if a file needs to be truncated')
    parser.add_argument(
        '-v',
        '--verbose',
        action='count',
        default=0,
        help="Output more info about what's being done. Repeated "
        "uses (-vv) will display even more info.")
    args = parser.parse_args()
    prog = parser.prog
    if not os.path.exists(args.source):
        raise Error('source path does not exist')
    args.source = os.path.abspath(args.source)
    if args.dest:
        args.dest = os.path.abspath(args.dest)
        if args.source.lower() == args.dest.lower():
            raise Error('source and destination arguments must be different')
    if args.dry_run:
        args.verbose = 2
        print('Note: This is a dry run; no changes are being made')