simfile-scripts/sort_by_difficulty.py

136 lines
4.4 KiB
Python
Raw Permalink Normal View History

2022-08-20 17:53:58 -07:00
R"""
Change the title of every simfile in a pack
so that they are sorted by difficulty in StepMania.
This script finds the hardest chart of a given stepstype (dance-single by default)
and puts its meter (difficulty number) between brackets at the start of the title.
Usage examples:
# Sort a pack by difficulty
python sort_by_difficulty.py "C:\StepMania\Songs\My Pack"
# Unsort by difficulty (remove the title prefixes)
python sort_by_difficulty.py -r "C:\StepMania\Songs\My Pack"
# Customize stepstype and digits
python sort_by_difficulty.py -s dance-double -d 3 "C:\StepMania\My Pack"
"""
2022-07-28 21:15:00 -07:00
import argparse
import sys
from typing import Optional, Sequence
import simfile
2022-08-20 17:53:58 -07:00
import simfile.dir
2022-07-28 21:15:00 -07:00
2022-08-20 17:53:58 -07:00
class SortByDifficultyArgs:
2022-07-28 21:15:00 -07:00
"""Stores the command-line arguments for this script."""
pack: str
stepstype: str
digits: int
remove: bool
def argparser():
"""Get an ArgumentParser instance for this command-line script."""
parser = argparse.ArgumentParser()
parser.add_argument("pack", type=str, help="path to the pack to modify")
parser.add_argument("-s", "--stepstype", type=str, default="dance-single")
parser.add_argument(
"-d",
"--digits",
type=int,
default=2,
help="minimum digits (will add leading zeroes)",
)
parser.add_argument(
"-r",
"--remove",
action=argparse.BooleanOptionalAction,
help="remove meter prefix",
)
return parser
def hardest_chart(
charts: Sequence[simfile.types.Chart], stepstype: str
) -> Optional[simfile.types.Chart]:
"""
Find & return the hardest chart (numerically) of a given stepstype.
Returns None if there are no charts matching the stepstype.
"""
return max(
[c for c in charts if c.stepstype == stepstype],
key=lambda c: int(c.meter or "1"),
default=None,
)
2022-08-20 17:53:58 -07:00
def prefix_title_with_meter(simfile_path: str, args: SortByDifficultyArgs):
2022-07-28 21:15:00 -07:00
"""
2022-08-20 17:53:58 -07:00
Add (or remove) a numeric prefix to the simfile's title.
2022-07-28 21:15:00 -07:00
This saves the updated simfile to its original location
and writes a backup copy with a ~ appended to the filename.
"""
# You could specify output_filename here to write the updated file elsewhere
with simfile.mutate(
input_filename=f"{simfile_path}",
backup_filename=f"{simfile_path}~",
) as sf:
2022-08-20 17:53:58 -07:00
print(f"Processing {simfile_path}")
2022-07-28 21:15:00 -07:00
# It's very unlikely for the title property to be blank or missing.
# This is mostly to satisfy type-checkers.
current_title = sf.title or ""
if args.remove:
# Look for a number in brackets at the start of the title
if current_title.startswith("["):
open_bracket_index = current_title.find("[")
close_bracket_index = current_title.find("]")
bracketed_text = current_title[
open_bracket_index + 1 : close_bracket_index
]
if bracketed_text.isnumeric():
# Remove the bracketed number from the title
sf.title = current_title[close_bracket_index + 1 :].lstrip(" ")
else:
# Find the hardest chart (numerically) within a stepstype
# and use it to prefix the title
chart = hardest_chart(sf.charts, args.stepstype)
# Skip this simfile if there were no charts for the stepstype.
# Nothing will be written to disk in this case.
if not chart:
raise simfile.CancelMutation
# It's very unlikely for the meter property to be blank or missing.
# This is mostly to satisfy type-checkers.
meter = chart.meter or "1"
# Put the meter at the start of the title,
# filling in leading zeros per arguments
sf.title = f"[{meter.zfill(args.digits)}] {current_title}"
def main(argv):
# Parse command-line arguments
2022-08-20 17:53:58 -07:00
args = argparser().parse_args(argv[1:], namespace=SortByDifficultyArgs())
2022-07-28 21:15:00 -07:00
# Iterate over SimfileDirectory objects from the pack
# so that we can easily get the .sm and/or .ssc paths
2022-08-20 17:53:58 -07:00
for simfile_dir in simfile.dir.SimfilePack(args.pack).simfile_dirs():
2022-07-28 21:15:00 -07:00
# Try to update whichever formats exist
for simfile_path in [simfile_dir.sm_path, simfile_dir.ssc_path]:
if simfile_path:
prefix_title_with_meter(simfile_path, args)
if __name__ == "__main__":
main(sys.argv)