import argparse import os import pprint import shelve import traceback from typing import Counter, Dict, List, Optional import simfile from simfile.notes import NoteData from simfile.notes.group import group_notes from simfile.notes.timed import time_notes from simfile.ssc import SSCChart from simfile.timing.engine import TimingData from simfile.timing.displaybpm import displaybpm class SimfileError: context: str traceback: Optional[str] def __init__(self, context: str): self.context = context self.traceback = traceback.format_exc() def __repr__(self): return f"{self.context}: {self.traceback}" def check_simfile(filename: str) -> List[SimfileError]: errors = [] try: sim = simfile.open(filename) except Exception: errors.append(SimfileError("parse")) return errors timing_data = None try: timing_data = TimingData.from_simfile(sim) except Exception: errors.append(SimfileError("timing")) try: displaybpm(sim) except Exception: errors.append(SimfileError("displaybpm")) for c, chart in enumerate(sim.charts): try: note_data = NoteData.from_chart(chart) except Exception: errors.append(SimfileError(f"chart {c} note_data")) try: for _ in group_notes(note_data, join_heads_to_tails=True): pass except Exception: errors.append(SimfileError(f"chart {c} group_notes")) if isinstance(chart, SSCChart): try: displaybpm(sim, chart) except Exception: errors.append(SimfileError(f"chart {c} displaybpm")) # skip the remaining chart tests if timing data parsing failed if timing_data is None: continue if isinstance(chart, SSCChart): try: timing_data = TimingData.from_simfile(sim, chart) except Exception: errors.append(SimfileError(f"chart {c} timing")) try: for _ in time_notes(note_data, timing_data): pass except Exception: errors.append(SimfileError(f"chart {c} timed_notes")) return errors def scan_directory(path: str, simfile_errors: Dict[str, List[SimfileError]]): stats = Counter() for entry in os.scandir(path): if simfile_errors.get(entry.path) == []: stats["skipped"] += 1 continue if entry.is_dir(): stats.update(scan_directory(entry.path, simfile_errors)) elif any(entry.name.endswith(ext) for ext in (".sm", ".ssc")): errors = check_simfile(entry.path) stats["checked"] += 1 simfile_errors[entry.path] = errors if errors: stats["error"] += 1 pprint.pprint({"path": entry.path, "errors": errors}) else: stats["success"] += 1 return stats def dir_path(string): if os.path.isdir(string): return string else: raise ValueError("dir_path: not a directory") def main(): argparser = argparse.ArgumentParser() argparser.add_argument( "directory", help="directory of simfiles to scan", type=dir_path, ) argparser.add_argument( "-s", "--shelf", help="shelf file to use", default=f"shelf-{simfile.__version__}", ) args = argparser.parse_args() with shelve.open(args.shelf) as simfile_errors: stats = scan_directory(args.directory, simfile_errors) for k, v in stats.items(): print(k, v) if __name__ == "__main__": main()