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 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')
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)
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
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
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
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
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 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')