def get_track_nbr_as_string(self, default='1', verbose=False): t = self.get('TRACKNUMBER') if not t and default is not None: if verbose: warn(self.get('TITLE'), 'has no track number, ' 'assuming track #1') t = default return t
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 get_filtered_songs(path, song_fs, artist_filter=None, album_filter=None, title_filter=None, sort_per_track=False, post_process_songs=False, print_header=True, no_warn=False, deep_warn=False, mark_missing_songs=False, dryrun=True, verbose=False, silent=False, debug=False): printed_header = False filtered_songs = {} missed_songs = [] warnings = 0 max_track_len = 0 total_album_tracks = None # unknown or inconsistent track_info_per_song = {} for song in song_fs: if is_song_file(song): s = Song(path=path, song_f=song, debug=debug) if ((not artist_filter or artist_filter.lower() in s.get('ALBUMARTIST').lower()) and ((not album_filter or album_filter.lower() in s.get('ALBUM').lower()) and (not title_filter or title_filter.lower() in s.get('TITLE').lower()))): if print_header and not printed_header: green() green('Listing', path) printed_header = True if sort_per_track: track_tag = s.get_track_nbr_as_string(verbose=verbose) track_nbr, total = s.parse_track_nbr(track_tag) track_info_per_song[s.name()] = (track_nbr, total) # build up a list, in case of mixes directory, there would # be track number overlaps f_songs = filtered_songs.get(track_nbr) if f_songs: f_songs.append(s) else: filtered_songs[track_nbr] = [s] else: track_tag = s.get_track_nbr_as_string(verbose=verbose) filtered_songs[len(filtered_songs)] = [s] max_track_len = max(max_track_len, len(track_tag)) if filtered_songs and sort_per_track and post_process_songs: prev_track_nbr = 0 song_that_gave_total_tracks = None total_tracks_is_inconsistent = False for track_nbr in sorted(filtered_songs): first_song = True for song in filtered_songs[track_nbr]: song_f = song.name() song_base = song_f[:-len(SONG_FILE_EXT)] track_nbr, total = track_info_per_song[song_f] if missing_file_exists(path, track_nbr): error('.missing file exists for', song_f, end='\n') error('You must delete it:', get_missing_file_full_path(path, track_nbr), end='\n') if first_song: if track_nbr != prev_track_nbr + 1: # TODO(if last song is missing, we don't report) for i in range(prev_track_nbr + 1, track_nbr): if not missing_file_exists(path, i): if mark_missing_songs: add_missing_file(path, i, dryrun) else: missed_songs.append((path, i)) prev_track_nbr = track_nbr first_song = False if total and not total_tracks_is_inconsistent: if total_album_tracks is None: total_album_tracks = total song_that_gave_total_tracks = song_base elif total_album_tracks != total: total_tracks_is_inconsistent = True total_album_tracks = None if not no_warn: warn('Within', path, 'album,') yellow(' ', song_base, 'has inconsistent total tracks with', song_that_gave_total_tracks) if deep_warn: print_songs(path, song_fs, artist_filter, album_filter, title_filter, sort_per_track=True, verbose=verbose, silent=silent, debug=debug) warnings += 1 if not silent: for song in missed_songs: warn(song[0], '[Track', song[1], '\b] is missing') warnings += 1 return (filtered_songs, missed_songs, total_album_tracks, warnings, max_track_len)
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 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 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
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import time, os, sys BATCHPATH = os.path.dirname( os.path.dirname(os.path.abspath(os.path.realpath(__file__)))) sys.path.insert(0, BATCHPATH) from lib.base import gettime, info, warn, error if __name__ == '__main__': tt = (1, 2, (30, 40)) info("hash(tt): {0}".format(hash(tt))) tl = (1, 2, [30, 40]) try: info("hash(tl): {0}".format(hash(tl))) except Exception as err: warn("throw: {0}".format(err)) tf = (1, 2, frozenset([30, 40])) info("hash(tf): {0}".format(hash(tf)))