def print(self, extended=False, highlight_title=False): for tag in self.get_tag_keys(extended): if highlight_title and tag == 'TITLE': bold('TITLE:', self.get(tag)) else: stdout(tag, ':', self.get(tag)) stdout()
def print_tags(self, detailed=False, as_json=False, as_pprint=False, to_file=None, extended=False, header=True, subscript=None, highlight_title=False): if header and not to_file: stdout() if subscript: green(self.full_path(), end='') cyan(' (' + subscript + ')') else: green(self.full_path()) track = self.get('TRACKNUMBER') fix_track = False if track.startswith('T'): track = track[1:] fix_track = True if fix_track: self.set('TRACKNUMBER', track) if detailed: self.print(extended, highlight_title) elif as_json or as_pprint: self.print_json(extended, as_pprint, to_file) else: self.echo()
def add_missing_file(path, track, dryrun=True): missed = get_missing_file_full_path(path, track) if dryrun: stdout('(dryrun)', missed, 'created') else: new_mark_file(missed) yellow(missed, 'created')
def fix_track_nbrs(root, project, artist, album, songs, artist_filter=None, album_filter=None, title_filter=None, force_overwrite_total_tracks=None, mark_missing_songs=False, dryrun=True, verbose=False, silent=False, radio_silent=False, debug=False): fixed_tracks = 0 missed_tracks = 0 warnings = 0 dry = '(dryrun) ' if dryrun else '' songs_by_track_nbr, missed_songs, total_tracks, w, _ = get_filtered_songs( root + '/' + project + '/' + artist + '/' + album, songs, artist_filter, album_filter, title_filter, sort_per_track=True, post_process_songs=force_overwrite_total_tracks is None, print_header=not silent, no_warn=radio_silent, deep_warn=True, mark_missing_songs=mark_missing_songs, dryrun=dryrun, verbose=verbose, silent=silent, debug=debug) missed_tracks += len(missed_songs) warnings += w if total_tracks or force_overwrite_total_tracks: for track_nbr in sorted(songs_by_track_nbr): for song in songs_by_track_nbr[track_nbr]: title = song.get('TITLE') track_tag = song.get_track_nbr_as_string(default=None, verbose=verbose) empty_tag = track_tag is None new_tag = '{}/{}'.format( track_nbr, force_overwrite_total_tracks if force_overwrite_total_tracks else total_tracks) if track_tag != new_tag: stdout( dry + 'Fixing', project, '/', artist, '/', album, '/', title, '/', 'empty track' if empty_tag else ('track ' + track_tag), 'to', new_tag) song.set('TRACKNUMBER', new_tag, save=True, dryrun=dryrun) fixed_tracks += 1 return fixed_tracks, missed_tracks, warnings
def sing_it(force=False): global sung_it # python 3 can do nonlocal but in py2 is readonly elaborated = False if not silent and (force or not sung_it): if elaborated: stdout(init_the_song, end='') # sing it else: yellow('Fixing', project, '/', artist_d, '/', album_d, '/', song_f) sung_it = True
def main(): program = 'fix_empty_dirs' description = ''' Deletes empty dirs''' parser = argparse.ArgumentParser(prog=program, description=description) parser.add_argument('-p', '--production', help='Enable production run', action='store_true') parser.add_argument('-v', '--verbose', help='Enable verbosity', action='store_true') parser.add_argument('-x', '--path', type=str, help='Root path') args = parser.parse_args() dryrun = not args.production or get_env_var('DRYRUN') verbose = args.verbose or get_env_var('VERBOSE') root = args.path or get_default_root() assert_non_empty_dir(root) mark_dry_or_production_run(dryrun) if dryrun: warn('Dryrun does not check nested empty dirs!') cnt = 0 done = False dryrun_deleted_dirs = [] while not done: cnt_start = cnt for dir_name, sub_dirs, file_names in os.walk(root): if verbose: yellow('Checking', dir_name) if not sub_dirs and not file_names: if dryrun: if dir_name in dryrun_deleted_dirs: continue else: dryrun_deleted_dirs.append(dir_name) remove_dir(dir_name, dryrun=dryrun, verbose=verbose) cnt += 1 done = cnt == cnt_start if cnt: stdout() stdout(cnt, 'dirs deleted')
def diff(self, other, my_name=None, other_name=None, extended=False, print_on_diff=True, print_on_diff_f=None): diff = False tags = self.get_tag_keys(extended) my_name = my_name or self.name() other_name = other_name or other.name() for tag in tags: if self.get(tag) != other.get(tag): if not diff and print_on_diff: if print_on_diff_f: stdout(print_on_diff_f) else: cyan(tag, 'diff:', self.get(tag), '(' + my_name + ') !=', other.get(tag), '(' + other_name + ')') diff = True break return diff
def finish_it(): global errors_fixed debug_out('finish_it') artist = song.get('ALBUMARTIST') contributing_artists = song.get('ARTIST') album = song.get('ALBUM') title = song.get('TITLE') if sung_it: report('OK') if did_it(): if silent: green('Retagged', project, '/', artist, '(' + contributing_artists + ') /', album, '/', title) else: stdout('WAS:') print_it(keep) stdout('BECAME:') print_it() save_it() # this is the moment where we save! errors_fixed += 1
def edit_song(song, debug=False): FILL_UP_TO = 15 done = False while not done: start_cyan() option(0, 'None') option(1, 'SONG FILE'.ljust(FILL_UP_TO) + song.name()) idx = 2 tag_qualifiers = Song.get_tag_keys() for tag in tag_qualifiers: option(idx, tag.ljust(FILL_UP_TO) + song.get(tag)) idx += 1 end_color() stdout() while True: t = numerical_input('Edit file/tag', 0, idx - 1) if t == 0: # exit done = True break elif t == 1: # new song file f = string_input('New SONG FILE', prefill=song.name()) yellow('Updating SONG FILE to', f) rename_file(song.path(), song.name(), f, dryrun=False, silent=True, debug=debug) song = Song(path=song.path(), song_f=f, debug=debug) stdout() else: t = tag_qualifiers[t - 2] f = string_input('New ' + t, prefill=song.get(t)) yellow('Updating', t, 'to', f) song.set(t, f, save=True, dryrun=False) # modify for real! stdout() return song
def main(): if is_py2(): warn('Running as python 3 is advised') stdout() program = 'audit_songs' description = ''' Audits songs or list of songs with its directory structure ''' parser = argparse.ArgumentParser(prog=program, description=description) parser.add_argument('-p', '--production', help='Enable production run', action='store_true') parser.add_argument('-j', '--project-filter', type=str, help='Set project filter') parser.add_argument('-a', '--artist-filter', type=str, help='Set artist filter') parser.add_argument('-b', '--album-filter', type=str, help='Set album filter') parser.add_argument('-c', '-t', '--title-filter', type=str, help='Set title filter') parser.add_argument('--dir-structure-as-ref', help=('Set the dir structure as the reference, ' 'opposed to the default\'s song tags'), action='store_true') parser.add_argument('--set-artist', type=str, help='Set (overrules) artists. Always use with ' '--artist-filter') parser.add_argument('--set-album', type=str, help='Set (overrules) album. Always use with ' '--album-filter') parser.add_argument('--set-song', type=str, help='Set (overrules) song title, Always use with ' '--title-filter') parser.add_argument('--provide-report', help='Provide a report of modified songs', action='store_true') parser.add_argument('-x', '--path', type=str, help='Sets root path') parser.add_argument('-n', '--limit-changes', type=int, help='Set a limit to amount of changes') parser.add_argument('-v', '--verbose', help='Enable verbosity', action='store_true') parser.add_argument('-s', '--silent', help='Enable silence', action='store_true') parser.add_argument('-d', '--debug', help='Enable debug', action='store_true') parser.add_argument('--force-write', help='Force-Write', action='store_true') args = parser.parse_args() root = args.path or get_default_root() dryrun = not args.production or get_env_var('DRYRUN') silent = args.silent or get_env_var('SILENT') debug = args.debug or get_env_var('DEBUG') verbose = args.verbose or get_env_var('VERBOSE') or debug provide_report = args.provide_report project_filter = args.project_filter artist_filter = args.artist_filter album_filter = args.album_filter title_filter = args.title_filter dir_structure_as_ref = args.dir_structure_as_ref set_artist = args.set_artist set_album = args.set_album set_song = args.set_song limit_changes = args.limit_changes or 9999999999 if set_artist and not artist_filter: fatal_error('Must set artist filter when setting artist') if set_album and not album_filter: fatal_error('Must set album filter when setting album') if set_song and not title_filter: fatal_error('Must set title filter when setting song title') if title_filter and title_filter.lower().endswith(SONG_FILE_EXT): title_filter = title_filter[:-len(SONG_FILE_EXT)] if set_song and set_song.lower().endswith(SONG_FILE_EXT): set_song = set_song[:-len(SONG_FILE_EXT)] assert_non_empty_dir(root) mark_dry_or_production_run(dryrun) for dir_name, _, filenames in os.walk(root): process, project, artist, album = process_songs_dir( root, dir_name, project_filter, artist_filter, album_filter) if not process: continue if not title_filter and verbose: yellow('Processing', project, artist, album) for song in filenames: if not song.lower().endswith(SONG_FILE_EXT): continue if title_filter: if title_filter.lower() != song[:-len(SONG_FILE_EXT)].lower(): continue elif verbose: yellow('Processing', project, artist, album, song) elif debug: yellow('Processing', project, artist, album, song) reset_artist = [None] reset_album = [None] if audit_song( root, project, artist, album, song, reset_artist, reset_album, dir_structure_as_ref=dir_structure_as_ref, # Once a first song in album fixes the artist, same artist # is dictated to rest of songs. This avoids ping-pong'ing. set_artist=set_artist if set_artist else None, # Once a first song in album fixes the album, same album is # dictated to rest of songs. This avoids ping-pong'ing. set_album=set_album if set_album else None, # User specified song set_song=set_song, force_write=args.force_write, dryrun=dryrun, verbose=verbose, silent=silent, debug=debug): if debug: stdout(errors_fixed, 'errors fixed') if reset_artist[0]: if (artist_filter and artist.lower() == artist_filter.lower()): artist_filter = reset_artist[0] artist = reset_artist[0] # Make sure next songs in album # will load correctly if reset_album[0]: if (album_filter and album.lower() == album_filter.lower()): album_filter = reset_album[0] album = reset_album[0] # Make sure next songs in album # will load correctly if not silent: stdout() if errors_fixed >= limit_changes: break if not silent and errors_fixed >= limit_changes: stdout() stdout(errors_fixed, 'errors were fixed;', errors_unresolved, 'remaining errors found') if provide_report and fixed_songs: stdout('Generating reports', ConsoleColors.ALERT) print_paths(fixed_songs, write_to_file=FIXED_SONGS_REPORT + '.cyg') print_paths(fixed_songs, dos_format=True, write_to_file=FIXED_SONGS_REPORT + '.dos') stdout(ConsoleColors.ENDC + 'Check the report:') stdout(' ', 'cat', FIXED_SONGS_REPORT + '.cyg') stdout(' ', 'cat', FIXED_SONGS_REPORT + '.dos') stdout() stdout('Feed into foobar2000 as:') stdout(' for i in $(cat ' + FIXED_SONGS_REPORT + '.dos); do songs="$songs $i"; done; foobar2000.exe /add $songs') stdout() stdout('Or do a little test:') stdout(' for i in $(cat ' + FIXED_SONGS_REPORT + '.dos); do echo "$i"; done') stdout()
def review_songs(path, song_files, title_filter, detailed=True, sort=True, verbose=False, silent=False, radio_silent=False, debug=False): songs = [] filtered_songs, missed_songs, _, _, _ = get_filtered_songs( path, song_files, title_filter=title_filter, sort_per_track=sort, post_process_songs=sort, print_header=not silent, verbose=verbose, silent=silent, no_warn=radio_silent, debug=debug) if not filtered_songs: return for track_nbr in sorted(filtered_songs): for song in filtered_songs[track_nbr]: songs.append(song) PAGE_SIZE = 20 done = False idx = 1 def option_title(title_idx): s = songs[title_idx - 1] # noinspection PyTypeChecker title = s.get('TITLE') if detailed: # noinspection PyTypeChecker track_nr = s.get('TRACKNUMBER') title += (' (' + track_nr + ')') option(title_idx, title) while not done: if idx > len(songs): break default = idx - 1 stdout() option(default, 'None') for _ in range(PAGE_SIZE): if idx > len(songs): done = True break option_title(idx) idx += 1 def add_options(_idx): _add_tags = None def _add_option(_s, _i): option(_i, _s) return _i, _i + 1 if missed_songs: _add_tags, _idx = _add_option('-- ADD .missing TAGS --', _idx) _reload, _idx = _add_option('-- RELOAD --', _idx) _exit_the_game, _idx = _add_option('-- EXIT --', _idx) return _add_tags, _reload, _exit_the_game add_tags, reload, exit_the_game = add_options(idx) stdout() while True: c = numerical_input('Song to edit', default, exit_the_game) if missed_songs and c == add_tags: stdout() for missed in missed_songs: add_missing_file(missed[0], missed[1], dryrun=False) stdout() c = None elif c == reload: stdout() option(default, 'None') for i in range(default + 2, idx): option_title(i) add_options(idx) cyan('(mind, not re-sorted)') cyan() c = None elif c == exit_the_game: cyan('Bye.') end() elif c == default: break if c is not None: stdout() song_idx = c - 1 songs[song_idx] = edit_song(songs[song_idx], debug=debug)
def print_it(p_tags=None, offset=4): p_tags = p_tags or song.get_tags() for _tag in Song.get_tag_keys(): if _tag == 'TRACKNUMBER': continue # skip stdout(' ' * offset + _tag + ': \'' + p_tags[_tag] + '\'')
def retag(song, new_title): if not silent: stdout('Retagging to', new_title) song.set('TITLE', new_title) song.save(dryrun)
def main(): program = 'fix_indexed_songs' description = ''' Fixes indexed songs''' parser = argparse.ArgumentParser(prog=program, description=description) parser.add_argument('-x', '--path', type=str, help='Root path') parser.add_argument('-v', '--verbose', help='Enable verbosity', action='store_true') parser.add_argument('-s', '--silent', help='Enable silence', action='store_true') parser.add_argument('-d', '--debug', help='Enable debug', action='store_true') parser.add_argument('-p', '--production', help='Enable production run', action='store_true') parser.add_argument( '--safe-delete', # create .deleted files always # Mind - for remixes can be good idea to set help='Safely delete files', action='store_true') args = parser.parse_args() root = args.path or get_default_root() dryrun = not args.production or get_env_var('DRYRUN') silent = args.silent or get_env_var('SILENT') debug = args.debug or get_env_var('DEBUG') verbose = args.verbose or get_env_var('VERBOSE') or debug safe_delete = args.safe_delete root = assure_not_endswith(root, '/') assert_non_empty_dir(root) mark_dry_or_production_run(dryrun) cnt = 0 if dryrun: warn('DRYRUN results are not representative!') if not safe_delete: warn('Redundant files WILL be deleted (to modify, give --safe-delete)') stdout(' ' '(While only redundant files are deleted, ' 'in case of remix songs, it can be dangerous. ') stdout(' ' ' In general, make sure diff songs have diff ' 'track numbers within a given dir.)') stdout() def report(*report_args, **report_kwargs): if not silent: cyan(*report_args, **report_kwargs) report('Reindexing files') while True: curr_cnt = cnt # # WARN : NOT MOST EFFICIENT IMPLEMENTATION! ... but it works :) # for dir_name, _, file_names in os.walk(root): if dir_name == root: continue else: unrooted = dir_name.replace(root + '/', '') cnt += fix_indexed_songs(root + '/' + unrooted, file_names, dryrun, safe_delete, verbose, silent, debug) changed_made = cnt - curr_cnt # keep looping if changes made in production run if dryrun or not changed_made: break # now check that we didn't create gaps, if so, fill them up report('Filling up holes') for dir_name, _, file_names in os.walk(root): unrooted = dir_name.replace(root + '/', '') fill_up_holes(root + '/' + unrooted, file_names, dryrun, verbose, silent) if cnt: stdout() stdout(cnt, 'files were renamed.')
def main(): program = 'diff_songs' description = ''' Diffs songs''' parser = argparse.ArgumentParser(prog=program, description=description) parser.add_argument('-p', '--production', help='Enable production run', action='store_true') parser.add_argument('-x1', '--path1', type=str, help='Path to music file 1 or dir 1 of music files') parser.add_argument('-x2', '--path2', type=str, help='Path to music file 2 or dir 2 of music files') parser.add_argument('-n1', '--name1', type=str, help='A name for music file 1 or dir 1 of music files') parser.add_argument('-n2', '--name2', type=str, help='A name for music file 2 or dir 2 of music files') parser.add_argument('-r', '--recursive', help='Treat paths recursively', action='store_true') parser.add_argument('-e', '--extended', help='Comparing also the song tags', action='store_true') parser.add_argument('-v', '--verbose', help='Enable verbosity', action='store_true') parser.add_argument('-d', '--debug', help='Enable debug', action='store_true') parser.add_argument('-m', '--minimal_verbose', help='Enable minimal verbosity', action='store_true') parser.add_argument('-s', '--synchronize', help='Synchronize differences', action='store_true') parser.add_argument('--ignore-not-existing', help='Ignore not-existing files', action='store_true') parser.add_argument('--ignore-file-differences', help='Ignore file differences', action='store_true') parser.add_argument('--ignore-files-only-dirs', help='Ignore files; only focuses on dirs', action='store_true') parser.add_argument('--delete-orphan-files', help='Delete orphan path2 files. ' 'CAUTION is to be applied!', action='store_true') args = parser.parse_args() dryrun = not args.production or get_env_var('DRYRUN') debug = args.debug or get_env_var('DEBUG') verbose = args.verbose or get_env_var('VERBOSE') or debug minimal_verbose = args.minimal_verbose or get_env_var('MINIMAL_VERBOSE') if args.production and not args.synchronize: fatal_error('Pointless production run while not synchronizing. ' 'Give -s -p instead.') mark_dry_or_production_run(dryrun) root1 = args.path1 root2 = args.path2 if not root1 or not root2: parser.print_help() return name1 = args.name1 or '1' name2 = args.name2 or '2' if args.recursive: if root1.endswith('/'): root1 = root1[:-1] # needed for checks below if root2.endswith('/'): root2 = root2[:-1] # needed for checks below if args.synchronize: stdout('=== Diff\'ing and Syncing music files ===\n') else: stdout('=== Diff\'ing music files ===\n') stdout('SOURCE:', root1) stdout('TARGET:', root2) stdout() cnt = diff_files(root1, root2, name1, name2, args.ignore_files_only_dirs, args.ignore_not_existing, args.ignore_file_differences, args.extended, args.synchronize, dryrun=dryrun, verbose=verbose, minimal_verbose=minimal_verbose, debug=debug) if minimal_verbose: stdout('\bCOMPLETE\n') elif cnt: stdout() diff_cnt = cnt if not args.ignore_not_existing and args.delete_orphan_files: stdout('CHECKING TARGET FOR ORPHANS (MIND!)') stdout() cnt = diff_files( root2, root1, name2, name1, # mind, swapped args.ignore_files_only_dirs, synchronize=args.synchronize, delete_root1_files_only=True, # in reality root2 dryrun=dryrun, verbose=verbose, minimal_verbose=minimal_verbose, debug=debug) if minimal_verbose: stdout('\bCOMPLETE\n') elif cnt: stdout() diff_cnt += cnt if diff_cnt: if args.synchronize: stdout(diff_cnt, 'diffs corrected') else: stdout(diff_cnt, 'diffs observed') else: stdout('No diff observed') elif diff_tags(root1, root2, name1, name2): red('Different') else: green('Equal')
def echo(self): stdout('[' + self.get('TRACKNUMBER'), self.get('ALBUM') + ']', end=' ') bold(self.get('TITLE'), end=' ') stdout('[' + self.get('ALBUMARTIST') + ']')
def main(): program = 'list_songs' description = ''' Lists a set of songs''' parser = argparse.ArgumentParser(prog=program, description=description) parser.add_argument('-x', '--path', type=str, help='Set root path') parser.add_argument('-a', '--artist-filter', type=str, help='Set artist filter') parser.add_argument('-b', '--album-filter', type=str, help='Set album filter') parser.add_argument('-c', '-t', '--title-filter', type=str, help='Set title filter') parser.add_argument('-np', '--no-paths', help='Don\'t print paths', action='store_true') parser.add_argument('-po', '--paths-only', help='Print paths only', action='store_true') parser.add_argument('--dos', help='Use DOS path format', action='store_true') parser.add_argument('--windows', help='Use Windows path format', action='store_true') parser.add_argument('--detailed', help='Enable detailed output', action='store_true') parser.add_argument('-e', '--extended', help='get extended tags (only matters when detailed', action='store_true') parser.add_argument('-v', '--verbose', help='Enable verbose', action='store_true') parser.add_argument('-s', '--silent', help='Enable silence', action='store_true') parser.add_argument('-d', '--debug', help='Enable debug', action='store_true') args = parser.parse_args() root = args.path or get_default_root() detailed = args.detailed extended = args.extended silent = args.silent or get_env_var('SILENT') debug = args.debug or get_env_var('DEBUG') verbose = args.verbose or get_env_var('VERBOSE') or debug root = assure_not_endswith(root, '/') assert_non_empty_dir(root) listed = 0 for dir_name, _, filenames in os.walk(root): c, _ = print_songs(dir_name, filenames, artist_filter=args.artist_filter, album_filter=args.album_filter, title_filter=args.title_filter, detailed=detailed, extended=extended, sort_per_track=True, warn_for_inconsistencies=not silent, print_paths=verbose and not args.no_paths, path_only=args.paths_only, dos_format=args.dos, windows_format=args.windows, verbose=verbose, silent=silent, debug=debug) if c and not verbose: stdout() listed += c stdout(listed, 'songs listed.')
def option(n, s): if 0 <= n <= 9: stdout(' [' + str(n) + ']', s) else: stdout('[' + str(n) + ']', s)
def main(): program = 'fix_track_nbrs' description = ''' Fixes song track numbers''' parser = argparse.ArgumentParser(prog=program, description=description) parser.add_argument('-x', '--path', type=str, help='Set root path') parser.add_argument('-j', '--project-filter', type=str, help='Set project filter') parser.add_argument('-a', '--artist-filter', type=str, help='Set artist filter') parser.add_argument('-b', '--album-filter', type=str, help='Set album filter') parser.add_argument('-c', '-t', '--title-filter', type=str, help='Set title filter') parser.add_argument('-m', '--missing-songs-stamps', help='Create missing songs stamps', action='store_true') parser.add_argument('--force-overwrite-total-tracks', type=int, help='Force-overwrite tracks total') parser.add_argument('-p', '--production', help='Enable production run', action='store_true') parser.add_argument('-v', '--verbose', help='Enable verbose', action='store_true') parser.add_argument('-s', '--silent', help='Enable silence', action='store_true') parser.add_argument('-rs', '--radio-silent', help='Enable radio silence', action='store_true') parser.add_argument('-d', '--debug', help='Enable debug', action='store_true') args = parser.parse_args() root = args.path or get_default_root() project_filter = args.project_filter artist_filter = args.artist_filter album_filter = args.album_filter title_filter = args.title_filter mark_missing_songs = args.missing_songs_stamps force_overwrite_total_tracks = args.force_overwrite_total_tracks dryrun = not args.production or get_env_var('DRYRUN') radio_silent = args.radio_silent or get_env_var('RADIO_SILENT') silent = args.silent or get_env_var('SILENT') or radio_silent debug = args.debug or get_env_var('DEBUG') verbose = args.verbose or get_env_var('VERBOSE') or debug root = assure_not_endswith(root, '/') # makes dir_name / filename to be # always correct assert_non_empty_dir(root) mark_dry_or_production_run(dryrun) if force_overwrite_total_tracks: assert artist_filter or album_filter or title_filter fixed = missed = warnings = 0 for dir_name, _, filenames in os.walk(root): process, project, artist, album = process_songs_dir( root, dir_name, project_filter, artist_filter, album_filter) if not process: continue f, m, w = fix_track_nbrs( root, project, artist, album, filenames, title_filter=title_filter, mark_missing_songs=mark_missing_songs, force_overwrite_total_tracks=force_overwrite_total_tracks, dryrun=dryrun, verbose=verbose, silent=silent, radio_silent=radio_silent, debug=debug) fixed += f missed += m warnings += w if fixed or (missed and not silent) or (warnings and not radio_silent): stdout() stdout(fixed, 'tracks were fixed.') if missed and not silent: stdout(missed, 'tracks are missed.') if warnings and not radio_silent: stdout(warnings, 'warnings were raised.')
def main(): program = 'copy_album_art' description = ''' Copies album art''' parser = argparse.ArgumentParser(prog=program, description=description) parser.add_argument('-p', '--production', help='production run', action='store_true') parser.add_argument('-x', '--path', type=str, help='path to music file or dir of music files') parser.add_argument('-a', '--album_art_path', type=str, help='destination path for music art') parser.add_argument('-r', '--recursive', help='treat paths recursively', action='store_true') parser.add_argument('-v', '--verbose', help='enables verbosity', action='store_true') parser.add_argument('-s', '--silent', help='enables silence', action='store_true') args = parser.parse_args() dryrun = not args.production or get_env_var('DRYRUN') verbose = args.verbose or get_env_var('VERBOSE') silent = args.silent or get_env_var('SILENT') root = args.path or get_default_root() assert_non_empty_dir(root) mark_dry_or_production_run(dryrun) dest = args.album_art_path or get_default_album_art_root(dryrun=dryrun, verbose=verbose) assert_non_empty_dir(dest) if root and dest: if args.recursive: if root.endswith('/'): root = root[:-1] # needed for checks below if dest.endswith('/'): dest = dest[:-1] # needed for checks below stdout('SOURCE:', root) stdout('TARGET:', dest) stdout() for dir_name, _, file_names in os.walk(root): if dir_name == root: continue else: unrooted = dir_name.replace(root + '/', '') make_dir(dest, unrooted, conditionally=True, dryrun=dryrun, verbose=verbose, silent=silent) for f in file_names: for album_art_ext in ALBUM_ART_EXT: if f.endswith(album_art_ext): copy_file(root + '/' + unrooted + '/' + f, dest + '/' + unrooted + '/' + f, dryrun=dryrun, verbose=verbose, silent=silent) else: stdout('Non-recursive not implemented yet') else: parser.print_help()
def diff_files(root1, root2, name1, name2, ignore_files_only_dirs=False, ignore_not_existing=False, ignore_file_differences=True, extended=False, synchronize=False, delete_root1_files_only=False, dryrun=True, verbose=False, minimal_verbose=False, debug=False): diff_cnt = 0 # minimal verbose: start index_c = '(' # always diff minimal_verbose_diff_cnt = -1 # minimal verbose: end for dir_name, _, file_names in os.walk(root1): if dir_name == root1: continue else: unrooted = dir_name.replace(root1 + '/', '') target_dir_name = dir_name.replace(root1, root2) if minimal_verbose: if '/' not in unrooted: cur_index_c = unrooted[0:1].upper() if cur_index_c != index_c: if minimal_verbose_diff_cnt != diff_cnt: stdout(PROCESSING, end='') minimal_verbose_diff_cnt = diff_cnt stdout('\b' + cur_index_c, end='') index_c = cur_index_c if not os.path.exists(target_dir_name) and not ignore_not_existing: if synchronize: if delete_root1_files_only: remove_dir(root1, unrooted, dryrun=dryrun, verbose=True) else: make_dir(root2, unrooted, dryrun=dryrun, verbose=True) else: if minimal_verbose: stdout('\b' * len(PROCESSING), end='') red(target_dir_name, 'does not exist') if ignore_files_only_dirs: continue for file_name in file_names: if is_supported_file_ext(file_name, include_shadow_files=False): base_file = dir_name + '/' + file_name target_file = target_dir_name + '/' + file_name if os.path.exists(target_file): if ignore_file_differences: continue action = (ConsoleColors.WARNING + 'DIFF .../{}/{}'.format(unrooted, file_name) + ConsoleColors.ENDC) if (extended and file_name.endswith(SONG_FILE_EXT) and diff_tags(base_file, target_file, name1, name2, print_on_diff_f=( ('\n' if minimal_verbose else '') + action + ConsoleColors.ALERT + ' DIFF' + ConsoleColors.ENDC) if not synchronize else None, debug=debug)): diff_cnt += 1 if synchronize: copy_file(root1 + '/' + unrooted + '/' + file_name, root2 + '/' + unrooted + '/' + file_name, safe_copy=False, dryrun=dryrun, verbose=True, debug=debug) elif verbose: stdout(action, end='') green(' OK') elif not ignore_not_existing: if synchronize: if delete_root1_files_only: remove_file(root1 + '/' + unrooted, file_name, safe_remove=False, dryrun=dryrun, verbose=verbose, debug=debug) else: copy_file(root1 + '/' + unrooted + '/' + file_name, root2 + '/' + unrooted + '/' + file_name, safe_copy=False, dryrun=dryrun, verbose=True, debug=debug) else: if minimal_verbose: stdout('\b' * len(PROCESSING), end='') red(target_file, 'does not exist') diff_cnt += 1 elif verbose: warn(file_name, 'is ignored in diff!') return diff_cnt
def debug_out(*args, **kwargs): if debug: stdout(ConsoleColors.ENDC, end='') stdout(*args, **kwargs)
def test_colours(): stdout('USA is ', end='') red('stars ', end='') stdout('and ', end='') blue('stripes') cyan('test_colours OK')
def main(): program = 'fix_non_flac' description = ''' Fix non-FLAC songs''' parser = argparse.ArgumentParser(prog=program, description=description) parser.add_argument('-x', '--path', type=str, help='Root path') parser.add_argument('-p', '--production', help='Enable production run', action='store_true') parser.add_argument('-ir', '--include-reviewed-files', help='Include .reviewed files', action='store_true') parser.add_argument('-f', '--full', help='Full clean, including tolerated extensions', action='store_true') parser.add_argument('-fa', '--full-including-album-art', help='Full clean, including album art and ' 'tolerated extensions', action='store_true') parser.add_argument('-v', '--verbose', help='Enable verbosity', action='store_true') parser.add_argument('-s', '--silent', help='Enable silence', action='store_true') parser.add_argument('-d', '--debug', help='Enable debug', action='store_true') args = parser.parse_args() root = args.path or get_default_root() dryrun = not args.production or get_env_var('DRYRUN') silent = args.silent or get_env_var('SILENT') debug = args.debug or get_env_var('DEBUG') verbose = args.verbose or get_env_var('VERBOSE') or debug assert_non_empty_dir(root) mark_dry_or_production_run(dryrun) cnt = 0 if args.full_including_album_art: protected_extensions = [] elif args.full: protected_extensions = ALBUM_ART_EXT elif args.include_reviewed_files: protected_extensions = MISSING_EXTENSIONS + ALBUM_ART_EXT else: protected_extensions = TOLERATED_FILE_EXTENSIONS + ALBUM_ART_EXT def protected(): for extension in protected_extensions: if file_name.lower().endswith(extension): return True return False for dir_name, _, file_names in os.walk(root): for file_name in file_names: if not is_song_file(file_name): if not protected(): yellow('Deleting', dir_name + '/' + file_name) remove_file(dir_name, file_name, False, dryrun=dryrun, verbose=verbose, silent=silent, debug=debug) cnt += 1 if cnt: stdout() stdout(cnt, 'non-{} files deleted.'.format(SONG_FILE_EXT[1:]))