diff --git a/src/saberlist/make.py b/src/saberlist/make.py index 329bc13..f00ee2b 100644 --- a/src/saberlist/make.py +++ b/src/saberlist/make.py @@ -46,45 +46,59 @@ def saberlist() -> None: Generate a playlist of songs using a specified strategy. Avoids reusing the same song+difficulty in a playlist based on history. """ - strategy = get_strategy() + args = parse_args_subcommands() + strategy = args.subcommand + # If the user requested a reset, do that before anything else + if getattr(args, 'reset', False): + reset_history(strategy) + sys.exit(0) + + # Then call the strategy-based logic if strategy == 'scoresaber_oldscores': - playlist_data, playlist_title = playlist_strategy_scoresaber_oldscores(ScoreSaberAPI(cache_expiry_days=CACHE_EXPIRY_DAYS)) + playlist_data, playlist_title = playlist_strategy_scoresaber_oldscores( + ScoreSaberAPI(cache_expiry_days=CACHE_EXPIRY_DAYS) + ) playlist_builder = PlaylistBuilder() + elif strategy == 'beatleader_oldscores': - playlist_data, playlist_title = playlist_strategy_beatleader_oldscores(BeatLeaderAPI(cache_expiry_days=CACHE_EXPIRY_DAYS)) + playlist_data, playlist_title = playlist_strategy_beatleader_oldscores( + BeatLeaderAPI(cache_expiry_days=CACHE_EXPIRY_DAYS) + ) playlist_builder = PlaylistBuilder() - elif strategy == 'beatsaver_acc': - playlist_data, playlist_title = playlist_strategy_beatsaver_acc() - playlist_builder = PlaylistBuilder(covers_dir='./covers/beatsavers') - elif strategy == 'beatleader_lowest_pp': - playlist_data, playlist_title = playlist_strategy_beatleader_lowest_pp(BeatLeaderAPI(cache_expiry_days=CACHE_EXPIRY_DAYS)) - playlist_builder = PlaylistBuilder(covers_dir='./covers/pajamas') - elif strategy == 'scoresaber_lowest_pp': - playlist_data, playlist_title = playlist_strategy_scoresaber_lowest_pp(ScoreSaberAPI(cache_expiry_days=CACHE_EXPIRY_DAYS)) - playlist_builder = PlaylistBuilder(covers_dir='./covers/scoresaber') - elif strategy == 'beatleader_lowest_acc': - playlist_data, playlist_title = playlist_strategy_beatleader_lowest_acc(BeatLeaderAPI(cache_expiry_days=CACHE_EXPIRY_DAYS)) - playlist_builder = PlaylistBuilder(covers_dir='./covers/kaiju') + elif strategy == 'beatleader_accuracy_gaps': - playlist_data, playlist_title = playlist_strategy_beatleader_accuracy_gaps(SimpleBeatLeaderAPI(cache_expiry_days=CACHE_EXPIRY_DAYS)) + playlist_data, playlist_title = playlist_strategy_beatleader_accuracy_gaps( + SimpleBeatLeaderAPI(cache_expiry_days=CACHE_EXPIRY_DAYS) + ) playlist_builder = PlaylistBuilder(covers_dir='./covers/pajamas') + elif strategy == 'beatleader_accuracy_gaps_star_range': - input_star_level = input("Enter star level (Default: 6)") or 6 - playlist_data, playlist_title = playlist_strategy_beatleader_accuracy_gaps_star_range(SimpleBeatLeaderAPI(cache_expiry_days=CACHE_EXPIRY_DAYS), star_level=float(input_star_level)) + playlist_data, playlist_title = playlist_strategy_beatleader_accuracy_gaps_star_range( + SimpleBeatLeaderAPI(cache_expiry_days=CACHE_EXPIRY_DAYS), + star_level=args.star_level + ) playlist_builder = PlaylistBuilder(covers_dir='./covers/pajamas') + elif strategy == 'scoresaber_accuracy_gaps': - playlist_data, playlist_title = playlist_strategy_scoresaber_accuracy_gaps(ScoreSaberAPI(cache_expiry_days=CACHE_EXPIRY_DAYS)) + playlist_data, playlist_title = playlist_strategy_scoresaber_accuracy_gaps( + ScoreSaberAPI(cache_expiry_days=CACHE_EXPIRY_DAYS) + ) playlist_builder = PlaylistBuilder(covers_dir='./covers/scoresaber') + elif strategy == 'beatsaver_curated': playlist_data, playlist_title = playlist_strategy_beatsaver_curated(SimpleBeatSaverAPI()) playlist_builder = PlaylistBuilder(covers_dir='./covers/curated') + elif strategy == 'beatsaver_mappers': playlist_data, playlist_title = playlist_strategy_beatsaver_mappers(SimpleBeatSaverAPI()) playlist_builder = PlaylistBuilder(covers_dir='./covers/pajamas') + elif strategy == 'blank_playlist': - playlist_data, playlist_title = [], input("Enter playlist title: ") + playlist_data = [] + playlist_title = input("Enter playlist title: ") playlist_builder = PlaylistBuilder(covers_dir='./covers/pajamas') + else: logging.error(f"Unknown strategy '{strategy}'") return @@ -99,41 +113,82 @@ def saberlist() -> None: playlist_author="SaberList Tool" ) -def get_strategy(): - parser = argparse.ArgumentParser(description="Generate Beat Saber playlists") - parser.add_argument("-s", "--strategy", - choices=[ - "scoresaber_oldscores", - "beatleader_oldscores", - # "beatsaver_acc", - # "beatleader_lowest_pp", - # "scoresaber_lowest_pp", - # "beatleader_lowest_acc", - "beatleader_accuracy_gaps", - "beatleader_accuracy_gaps_star_range", - "scoresaber_accuracy_gaps", - "beatsaver_curated", - "beatsaver_mappers", - "blank_playlist" - ], - help="Specify the playlist generation strategy") - parser.add_argument("-r", "--reset", - action="store_true", - help="Reset the history for the specified strategy") - +def parse_args_subcommands(): + """ + Parse sub-commands for each strategy instead of using a single `-s/--strategy`. + """ + parser = argparse.ArgumentParser( + description="Generate Beat Saber playlists" + ) + + subparsers = parser.add_subparsers( + title="Available Strategies", + dest="subcommand", + help="Choose which sub-command (strategy) to run" + ) + + # 1) -------- scoresaber_oldscores -------- + parser_ss_old = subparsers.add_parser("scoresaber_oldscores", + help="Generate a playlist using ScoreSaber old-scores strategy") + parser_ss_old.add_argument("-r", "--reset", + action="store_true", + help="Reset the history for scoresaber_oldscores") + + # 2) -------- beatleader_oldscores -------- + parser_bl_old = subparsers.add_parser("beatleader_oldscores", + help="Generate a playlist using BeatLeader old-scores strategy") + parser_bl_old.add_argument("-r", "--reset", + action="store_true", + help="Reset the history for beatleader_oldscores") + + # 3) -------- beatleader_accuracy_gaps -------- + parser_bl_acc_gaps = subparsers.add_parser("beatleader_accuracy_gaps", + help="Generate a playlist using BeatLeader accuracy gaps strategy") + parser_bl_acc_gaps.add_argument("-r", "--reset", + action="store_true", + help="Reset the history for beatleader_accuracy_gaps") + + # 4) -------- beatleader_accuracy_gaps_star_range -------- + parser_bl_acc_stars = subparsers.add_parser("beatleader_accuracy_gaps_star_range", + help="Generate a playlist for accuracy gaps within a star range (BeatLeader)") + parser_bl_acc_stars.add_argument("-r", "--reset", + action="store_true", + help="Reset the history for beatleader_accuracy_gaps_star_range") + parser_bl_acc_stars.add_argument("--star-level", + type=float, + help="Star level to filter on") + + # 5) -------- scoresaber_accuracy_gaps -------- + parser_ss_acc_gaps = subparsers.add_parser("scoresaber_accuracy_gaps", + help="Generate a playlist using ScoreSaber accuracy gap strategy") + parser_ss_acc_gaps.add_argument("-r", "--reset", + action="store_true", + help="Reset the history for scoresaber_accuracy_gaps") + + # 6) -------- beatsaver_curated -------- + parser_bs_curated = subparsers.add_parser("beatsaver_curated", + help="Generate a curated BeatSaver playlist") + parser_bs_curated.add_argument("-r", "--reset", + action="store_true", + help="Reset the history for beatsaver_curated") + + # 7) -------- beatsaver_mappers -------- + parser_bs_mappers = subparsers.add_parser("beatsaver_mappers", + help="Generate a playlist for specified BeatSaver mappers") + parser_bs_mappers.add_argument("-r", "--reset", + action="store_true", + help="Reset the history for beatsaver_mappers") + + # 8) -------- blank_playlist -------- + parser_blank = subparsers.add_parser("blank_playlist", + help="Generate a blank playlist (no songs, just a descriptor)") + parser_blank.add_argument("-r", "--reset", + action="store_true", + help="Reset the history for blank_playlist (usually unnecessary)") + + # If no arguments passed, print help if len(sys.argv) == 1: - parser.print_help() + parser.print_help(sys.stderr) sys.exit(1) - - args = parser.parse_args() - - if args.reset: - if not args.strategy: - parser.error("--reset requires --strategy to be specified") - reset_history(args.strategy) - sys.exit(0) - - if not args.strategy: - parser.error("--strategy is required unless --reset is used") - - return args.strategy + + return parser.parse_args() diff --git a/src/saberlist/playlist_strategies/accuracy.py b/src/saberlist/playlist_strategies/accuracy.py index 2c38340..9b1b13f 100644 --- a/src/saberlist/playlist_strategies/accuracy.py +++ b/src/saberlist/playlist_strategies/accuracy.py @@ -417,7 +417,9 @@ def playlist_strategy_beatleader_accuracy_gaps_star_range( history.setdefault('beatleader_accuracy_gaps_star_range', {}) history.setdefault('playlist_counts', {}) - # Validate star_level + if not star_level: + input_star_level = input("Enter star level (Default: 6): ") or 6 + star_level = float(input_star_level) if not isinstance(star_level, (int, float)) or star_level < 0: logging.error("Invalid star_level provided. It must be a non-negative number.") return [], ""