def main(): program = 'set_tags' description = ''' Sets tags of a music file. Set to 'None' to clear.''' parser = argparse.ArgumentParser(prog=program, description=description) tag_options = { 'album': '-a', 'albumartist': '-b', 'artist': '-c', # contributing artists 'title': '-t', 'tracknumber': '-n' } extended_tag_options = [ 'artwork', 'comment', 'compilation', 'composer', 'discnumber', 'genre', 'lyrics', 'year', 'totaldiscs', 'totaltracks' ] parser.add_argument('-p', '--production', help='Enable production run', action='store_true') parser.add_argument('-d', '--debug', help='Enable debug', action='store_true') parser.add_argument('-x', '--path', type=str, help='path to music file') for tag, shortcut in tag_options.items(): parser.add_argument(shortcut, '--' + tag, type=str, help=tag) for tag in extended_tag_options: parser.add_argument('--' + tag, type=str, help=tag) args = parser.parse_args() dryrun = not args.production or get_env_var('DRYRUN') debug = args.debug or get_env_var('DEBUG') parse_ok = False kwargs = {} if args.path: for t in list(tag_options) + extended_tag_options: if hasattr(args, t) and getattr(args, t): kwargs[t] = getattr(args, t) parse_ok = True if parse_ok: mark_dry_or_production_run(dryrun) set_and_print(args.path, 'Unmodified tags' if dryrun else None, dryrun, debug, **kwargs) else: parser.print_help()
def get_default_root(): global DEFAULT_ROOT if not DEFAULT_ROOT: DEFAULT_ROOT = get_env_var('MUSIC') if not DEFAULT_ROOT: DEFAULT_ROOT = '{}/{}'.format(get_home(), DEFAULT_MUSIC_ROOT) return DEFAULT_ROOT
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_default_album_art_root(dryrun=True, verbose=False): global DEFAULT_ALBUM_ART_ROOT, DEFAULT_ALBUM_ART_SUB_PATH global DEFAULT_ALBUM_ART_SUB_SUB_PATH if not DEFAULT_ALBUM_ART_ROOT: DEFAULT_ALBUM_ART_ROOT = get_env_var('ALBUM_ART') if not DEFAULT_ALBUM_ART_ROOT: DEFAULT_ALBUM_ART_ROOT = '{}/{}'.format( get_home(), DEFAULT_ALBUM_ART_SUB_PATH) make_dir(DEFAULT_ALBUM_ART_ROOT, DEFAULT_ALBUM_ART_SUB_SUB_PATH, conditionally=True, dryrun=dryrun, verbose=verbose) DEFAULT_ALBUM_ART_ROOT += '/' + DEFAULT_ALBUM_ART_SUB_SUB_PATH return DEFAULT_ALBUM_ART_ROOT
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 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:]))
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 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 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 main(): program = 'review_songs' description = ''' Review songs based on user input ''' parser = argparse.ArgumentParser(prog=program, description=description) 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('-x', '--path', type=str, help='Sets root path') parser.add_argument('-r', '--review-stamps', help='Create/Use review stamps', 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() 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 assert_non_empty_dir(root) project_filter = args.project_filter artist_filter = args.artist_filter album_filter = args.album_filter title_filter = args.title_filter review_stamps = args.review_stamps if title_filter and title_filter.lower().endswith(SONG_FILE_EXT): title_filter = title_filter[:-len(SONG_FILE_EXT)] for dir_name, _, song_fs in os.walk(root): # gotcha... this doesn't work if i specify a full detailed path, up # to the album dir ... process, project, artist, album = process_songs_dir( root, dir_name, project_filter, artist_filter, album_filter) if process: path = root + '/' + project + '/' + artist + '/' + album reviewed_mark = path + '/.reviewed' if review_stamps and is_file(reviewed_mark): if debug: cyan('Skipping', project, '/', artist, '/', album) else: review_songs(path, song_fs, title_filter, verbose=verbose, silent=silent, radio_silent=radio_silent, debug=debug) if review_stamps: new_mark_file(reviewed_mark)