diff --git a/.gitignore b/.gitignore index 9b2352f..386824d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ testdata/ -output/ \ No newline at end of file +packs/ \ No newline at end of file diff --git a/aliases/usedaliases.txt b/aliases/usedaliases.txt index 05586fb..0d3e996 100644 --- a/aliases/usedaliases.txt +++ b/aliases/usedaliases.txt @@ -1,3 +1,10 @@ +========== Banned alias positions ========== +* The +* #090 +* もしン +* CAR???? +* Non +東京 * ========== (R2) AGEN WIDA ========== Angry Squid Blonde Fox @@ -657,4 +664,169 @@ StepMania Swearing Flavorless Boys Bullet Refutal Vinyl Countdown -Blue Vocals \ No newline at end of file +Blue Vocals +========== dimocracy 2024 R1 - Bee, Ex, or Eff ========== +Swear Meteor +Enormous Glue +Extraterrestrial Jalapeño +Very Jazz +The もしン +Moonlight Eggo +Metal Murray +Wrist Swing +Team Brick +Two Island +Metal Banger +Slow Island +Lively Wave +None Emerald +Refridgerating Spoon +Legal Skirt +Leaping Pancake +Jungle Factory +Punishment Days +Disturbed Bag +Pink Committee +Activate Elf +Adventurous Ey +Obtain Thumping +Scream Cat +Last Head +Pink Opinion +Zodiac Literature +Redundant Sun +Spruious Salamander +Spiky Insurance +Boats Song +Makita Hi-Tech +Omega Rainbow +Steep Loss +Bubba Wave +Scatter Sorcerer +This Burst +Body Cucumbers +Teeny-Tiny Side +Expo Calendar +Smite Past +Alice Ten +Heat Oyster +Emotion Lotion +Spruious Hi-Tech +Adventurous Year +Volcano Lagoon +Cuba Shelter +Shiver Wave +Last Brother +Snail Okay +Nice Form +Hope Alternative +Attempt Jellyfish +Magic Breath +Makita Canine +Turnt Swung +Tekken Bubble +Obscene Loss +Extraterrestrial Glue +Geese Curtain +Repeat Balance +========== dimocracy 2024 R3 - Fantastic Artists ========== +Smite Power +Adaptable Graph +Blunt Spoon +Hop Lemons +Lethal Harpy +Collect Hill +Ripe Pants +Peaceful Chilver +Pioneer Volleyball +False Ray +Inconspicuous Fun +Adaptable Example +Pokemon Goggles +Fantastic Bear +Round Rat +Drayze Argument +Ghost Example +Salami Seagull +Fashionable Eleven +Tranquil Power +Troubleshoot Hi-Tech +Feldspar Tooth +Suspend On +Lively Poser +Strip Cannon +Lick Shoes +Fashionable Over +Happy Rest +Volcano Jambalaya +Two Thumping +Uncharacteristically Stream +Penitent Want +Stylish Idea +Sweet Stream +Try #090 +Expect Rag +Nice Mania +Pleasant Solid +Adaptable Wolf +Hard-To-Find Sphere +Jellyfish Use +Alien #090 +Copy Wave +Chew Haganuma +Purple Hacker +Sweet Music +========== dimocracy 2024 R4 - IdK! International dimocr4cy Karaoke! ========== +A Holds +Milky Wild +Basketball Bunions +Bite Dandy +Grateful Mage +New Impersonate +Down Off +Flam Expert +Devilish Scarecrow +Stocking Outside +Centrifuging Fight +Algebra Smuggler +Blind Fortress +Song Magenta +Temple Swords +Atlanta Hoagie +Seventeen Help +Modular Aftermath +Pixel Fantastics? +Disagreeable Days +Heat Hacker +Akiba CAR???? +Marvelous Challenge +Blonde Rear +Courageous Dance +Disturbed Ey +Fretful Lemons +Peaceful Bane +Sixty Power +Scream Offset +Marvelous Gorilla +Construct Weevils +Ride Soup +Chief Time +Steep Bag +Simon #090 +Dark Past +Cream Design +Pixel Floor +Palma NB +Modular Statement +Nice Gamer +Soothing Burst +Summer Style +Steep Gadzooks +Extraterrestrial Weevils +More Pickles +Wiggly Silver +Good Pumpkin +Whillikers #090 +Construct Patient +Somber Axolotl +Milky Knot \ No newline at end of file diff --git a/anonymize_entries.py b/anonymize_entries.py index 01587e7..6409984 100644 --- a/anonymize_entries.py +++ b/anonymize_entries.py @@ -18,14 +18,16 @@ from fs.base import FS from fs.copy import copy_dir from fs.tempfs import TempFS from fs.zipfs import ZipFS +from msdparser import MSDParserError from pathvalidate import sanitize_filename import simfile from simfile.dir import SimfilePack, SimfileDirectory from simfile.notes import NoteData from simfile.sm import SMChart, SMSimfile from simfile.ssc import SSCChart, SSCSimfile -from simfile.timing import BeatValues, BeatValue -from simfile.types import Simfile, Chart +from simfile.tidy import tidy, Preset, RemoveComments +from simfile.timing import Beat, BeatValues, BeatValue +from simfile.types import Simfile, Chart, AttachedChart #################### @@ -143,9 +145,11 @@ CsvContents = list[dict[str, str]] class KnownColumns(enum.StrEnum): Timestamp = "Timestamp" - UserId = "Your gamer tag/alias: (e.g. dimo)" + UserId = "Your gamer tag: (e.g. dimo)" GeneratedAlias = "Generated Alias" IgnoreFile = "Ignore File" + SongTitle = "Song Title" + SongArtist = "Song Artist" # Not persisted: ExtractedTo = "Extracted To" @@ -159,11 +163,32 @@ ChangedCsvContents = bool def parse_timestamp(timestamp: str): - return datetime.strptime(timestamp, "%m/%d/%Y %H:%M:%S") + return datetime.strptime( + timestamp, + "%Y/%m/%d %I:%M:%S %p" # it changed for some reason + # "%m/%d/%Y %H:%M:%S" + ) def canonical_simfile_filename(sm: Simfile) -> str: - return sanitize_filename(f"{sm.title} {sm.subtitle or ''}".rstrip()) + title_name = f"{sm.titletranslit or sm.title} {sm.subtitletranslit or sm.subtitle or ''}".rstrip() + # HACK: replace this with proper duplicate handling later + title_name = ( + title_name.replace("❤", "red_heart") + .replace("💚", "green_heart") + .replace("💛", "yellow_heart") + .replace("💙", "blue_heart") + .replace("💜", "purple_heart") + .replace("🖤", "black_heart") + .replace("🔶", "orange_diamond") + .replace("🔷", "blue_diamond") + .replace("🔴", "red_circle") + .replace("🔵", "blue_circle") + ) + ascii_title_name = "".join([i if ord(i) < 128 else "_" for i in title_name]) + if all((not c.isalnum() for c in ascii_title_name)): + return f"Non-ASCII Title {hash(title_name) % (2**16):x}" + return sanitize_filename(ascii_title_name) ################ @@ -280,7 +305,12 @@ def maybe_generate_aliases( for row in csv_contents: rnd = Random(",".join([row[KnownColumns.UserId], seed])) while True: - random_alias = f"{rnd.choice(alias_parts[0])} {rnd.choice(alias_parts[1])}" + # ad-hoc code for round 2 (3-way collab): + # either_part = [*alias_parts[0], *alias_parts[1]] + # random_alias = f"{rnd.choice(alias_parts[0])} {rnd.choice(either_part)} {rnd.choice(alias_parts[1])}" + random_alias_left = rnd.choice(alias_parts[0]) + random_alias_right = rnd.choice(alias_parts[1]) + random_alias = f"{random_alias_left} {random_alias_right}" if ( random_alias in alias_to_user_id and alias_to_user_id[random_alias] != row[KnownColumns.UserId] @@ -288,7 +318,11 @@ def maybe_generate_aliases( print( f"Rerolling alias for {row[KnownColumns.UserId]} because {repr(random_alias)} is already being used by {alias_to_user_id[random_alias]}" ) - elif random_alias.lower() in usedaliases: + elif ( + random_alias.lower() in usedaliases + or f"* {random_alias_right.lower()}" in usedaliases + or f"{random_alias_left.lower()} *" in usedaliases + ): print( f"Rerolling alias for {row[KnownColumns.UserId]} because {repr(random_alias)} has already been used" ) @@ -407,10 +441,14 @@ def extract_entries_to_temporary_folder( def extract_simfile_dir(zip_fs: FS, temp_fs: FS) -> str: zip_path, simfile_dir = find_simfile_dir_zip_path(zip_fs) - canonical_filename = canonical_simfile_filename(simfile_dir.open()) + try: + canonical_filename = canonical_simfile_filename(simfile_dir.open()) + except MSDParserError: + print("Exception encountered while processing", simfile_dir) + raise assert not temp_fs.exists( canonical_filename - ), "ERROR: trying to extract {canonical_filename} but it's already present in the temp folder" + ), f"ERROR: trying to extract {canonical_filename} but it's already present in the temp folder" copy_dir(zip_fs, zip_path, temp_fs, canonical_filename) return canonical_filename @@ -455,7 +493,7 @@ def maybe_anonymize_entries( ) os.rename(absolute_path, absolute_canonical_path) print( - f"Renamed {os.path.relpath(absolute_path, temp_fs.root_path)} to {os.path.relpath(absolute_canonical_path, temp_fs.root_path)}" + f"Renaming {os.path.relpath(absolute_path, temp_fs.root_path)} to {os.path.relpath(absolute_canonical_path, temp_fs.root_path)}" ) def maybe_delete_file(absolute_path: str | None): @@ -463,19 +501,24 @@ def maybe_anonymize_entries( os.remove(absolute_path) print(f"Deleted {os.path.relpath(absolute_path, temp_fs.root_path)}") - def anonymize_bpms(bpm_str: str | None) -> str: + def anonymize_bpms(obj: Simfile | SSCChart, last_beat: Beat) -> None: + bpm_str = obj.bpms bpm_values = BeatValues.from_str(bpm_str) + + if not obj.displaybpm and len(bpm_values) == 1: + obj.displaybpm = str(int(bpm_values[0].value)) + bpm_values.append( BeatValue( - beat=bpm_values[-1].beat + 10000, + beat=last_beat + 4, value=bpm_values[-1].value + Decimal("0.001"), ) ) + obj.bpms = str(bpm_values) print(f"Anonymized BPMs from {repr(bpm_str)} to {repr(str(bpm_values))}") - return str(bpm_values) - def clean_up_difficulties(sf: Simfile): - charts_to_remove: list[Chart] = [] + def clean_up_difficulties(sf: Simfile) -> Chart: + charts_to_remove: list[AttachedChart] = [] chart_with_notes = None for _chart in sf.charts: @@ -505,7 +548,45 @@ def maybe_anonymize_entries( print( f"WARNING: removing {chart_to_remove.difficulty} chart with no notes from {canonical_filename}" ) - sm.charts.remove(chart_to_remove) + sm.charts.remove(chart_to_remove) # type: ignore + + return chart_with_notes + + def anonymize_simfile(sf: Simfile): + if sf.titletranslit == "Chong wo": + print(f"Assigning credit to {row[KnownColumns.GeneratedAlias]}") + sf.credit = row[KnownColumns.GeneratedAlias] + sf.music = f"{canonical_filename}.ogg" + sf.background = "" + sf.banner = "" + sf.cdtitle = "" + sf.genre = "" + sf.music = f"{canonical_filename}.ogg" + + if isinstance(sf, SSCSimfile): + sf.jacket = "" + sf.cdimage = "" + sf.discimage = "" + sf.labels = "" + + chart = clean_up_difficulties(sf) + + last_beat = Beat(0) + for note in NoteData(chart): + last_beat = note.beat + anonymize_bpms(sf, last_beat) + + if isinstance(chart, SMChart): + chart.description = row[KnownColumns.GeneratedAlias] + else: + chart.credit = row[KnownColumns.GeneratedAlias] + chart.description = "" + chart.chartname = "" + chart.chartstyle = "" + if chart.bpms: + anonymize_bpms(chart, last_beat) + + tidy(sf, Preset.RECOMMENDED, remove_comments=RemoveComments.ALL) for row in csv_contents: if row[KnownColumns.IgnoreFile]: @@ -529,18 +610,7 @@ def maybe_anonymize_entries( if simfile_dir.sm_path: with simfile.mutate(simfile_dir.sm_path) as sm: assert isinstance(sm, SMSimfile) - sm.credit = row[KnownColumns.GeneratedAlias] - sm.background = "" - sm.banner = "" - sm.cdtitle = "" - sm.genre = "" - sm.music = f"{canonical_filename}.ogg" - sm.bpms = anonymize_bpms(sm.bpms) - clean_up_difficulties(sm) - - for _chart in sm.charts: - sm_chart: SMChart = _chart # typing workaround - sm_chart.description = row[KnownColumns.GeneratedAlias] + anonymize_simfile(sm) maybe_rename_file(simfile_dir.sm_path, f"{canonical_filename}.sm") print( f"Scrubbed {os.path.relpath(absolute_simfile_dir_path, temp_fs.root_path)}/{canonical_filename}.sm" @@ -549,26 +619,7 @@ def maybe_anonymize_entries( if simfile_dir.ssc_path: with simfile.mutate(simfile_dir.ssc_path) as ssc: assert isinstance(ssc, SSCSimfile) - ssc.credit = row[KnownColumns.GeneratedAlias] - ssc.music = f"{canonical_filename}.ogg" - ssc.background = "" - ssc.banner = "" - ssc.cdtitle = "" - ssc.genre = "" - ssc.jacket = "" - ssc.cdimage = "" - ssc.discimage = "" - ssc.labels = "" - ssc.bpms = anonymize_bpms(ssc.bpms) - clean_up_difficulties(ssc) - for _chart in ssc.charts: - ssc_chart: SSCChart = _chart # typing workaround - ssc_chart.description = "" - ssc_chart.chartname = "" - ssc_chart.chartstyle = "" - ssc_chart.credit = row[KnownColumns.GeneratedAlias] - if ssc_chart.bpms: - ssc_chart.bpms = anonymize_bpms(ssc_chart.bpms) + anonymize_simfile(ssc) maybe_rename_file(simfile_dir.ssc_path, f"{canonical_filename}.ssc") print( f"Scrubbed {os.path.relpath(absolute_simfile_dir_path, temp_fs.root_path)}/{canonical_filename}.ssc" @@ -583,6 +634,7 @@ def maybe_anonymize_entries( or dir_entry.name.endswith(".txt") or dir_entry.name.endswith(".zip") or dir_entry.name == ".DS_Store" + or dir_entry.name == "desktop.ini" ): # These are definitely safe to delete for distribution os.remove(dir_entry.path) @@ -616,7 +668,7 @@ def maybe_save_anonymized_files( args: AnonymizeEntriesArgs, csv_contents: CsvContents, temp_fs: TempFS, -): +) -> str | None: if args.dry_run: print("Dry run - not saving files") return @@ -625,6 +677,39 @@ def maybe_save_anonymized_files( output_path = f"{args.output}/{de}anonymized-{timestamp}" shutil.copytree(temp_fs.root_path, output_path) print(f"Saved to {os.path.abspath(output_path)}") + return output_path + + +def maybe_generate_song_metadata( + args: AnonymizeEntriesArgs, + csv_contents: CsvContents, + output_path: str, +): + reuse_song_metadata = ( + not args.regenerate + and KnownColumns.SongTitle in csv_contents[0] + and KnownColumns.SongArtist in csv_contents[0] + ) + + if reuse_song_metadata: + print("Reusing generated song metadata") + return False + + alias_to_simfile: dict[str, Simfile] = {} + for sf, sf_path in simfile.openpack(output_path): + assert sf.credit, f"{sf_path} was generated without a credit?!" + alias_to_simfile[sf.credit] = sf + + for row in csv_contents: + alias = row[KnownColumns.GeneratedAlias] + sf = alias_to_simfile[alias] + row[KnownColumns.SongTitle] = f"{sf.title} {sf.subtitle or ''}".rstrip() + row[KnownColumns.SongArtist] = sf.artist or "" + print( + f"Mapped {alias} to {row[KnownColumns.SongTitle]} by {row[KnownColumns.SongArtist]}" + ) + + return True ############### @@ -652,7 +737,13 @@ def main(argv: list[str]): temp_fs = extract_entries_to_temporary_folder(args, csv_contents, dynamic_columns) maybe_anonymize_entries(args, csv_contents, temp_fs) - maybe_save_anonymized_files(args, csv_contents, temp_fs) + output_path = maybe_save_anonymized_files(args, csv_contents, temp_fs) + if output_path: + csv_contents_changed = maybe_generate_song_metadata( + args, csv_contents, output_path + ) + if csv_contents_changed: + maybe_save_generated_columns(args, csv_contents) if __name__ == "__main__": diff --git a/poetry.lock b/poetry.lock index 1888a9b..268775f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,33 +13,33 @@ files = [ [[package]] name = "black" -version = "24.8.0" +version = "24.10.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, ] [package.dependencies] @@ -51,19 +51,19 @@ platformdirs = ">=2" [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] @@ -101,13 +101,13 @@ scandir = ["scandir (>=1.5,<2.0)"] [[package]] name = "msdparser" -version = "2.0.0" -description = "Robust & lightning fast MSD parser (StepMania file format)" +version = "3.0.0a7" +description = "Low-level parser for StepMania MSD files (SM, SSC, DWI...)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.10" files = [ - {file = "msdparser-2.0.0-py3-none-any.whl", hash = "sha256:bbef8c89b2dc16183ba714354b093fff11a43469971c42972284c5046826002d"}, - {file = "msdparser-2.0.0.tar.gz", hash = "sha256:9bb6cf4b705c76850b1ce22823c768b62ba2bb63d1c2407bbe3e2c23e38be8e3"}, + {file = "msdparser-3.0.0a7-py3-none-any.whl", hash = "sha256:ef324aa943e325bc1c1ef6b363bf55433def3deb8a9f2fa608b14eff828ccf64"}, + {file = "msdparser-3.0.0a7.tar.gz", hash = "sha256:797bc6a99ae491cf2712483d2e0fb086ac4e047354cc8f8e4712bce95833c6c6"}, ] [[package]] @@ -123,13 +123,13 @@ files = [ [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -145,78 +145,100 @@ files = [ [[package]] name = "pathvalidate" -version = "3.2.0" +version = "3.2.3" description = "pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pathvalidate-3.2.0-py3-none-any.whl", hash = "sha256:cc593caa6299b22b37f228148257997e2fa850eea2daf7e4cc9205cef6908dee"}, - {file = "pathvalidate-3.2.0.tar.gz", hash = "sha256:5e8378cf6712bff67fbe7a8307d99fa8c1a0cb28aa477056f8fc374f0dff24ad"}, + {file = "pathvalidate-3.2.3-py3-none-any.whl", hash = "sha256:5eaf0562e345d4b6d0c0239d0f690c3bd84d2a9a3c4c73b99ea667401b27bee1"}, + {file = "pathvalidate-3.2.3.tar.gz", hash = "sha256:59b5b9278e30382d6d213497623043ebe63f10e29055be4419a9c04c721739cb"}, ] [package.extras] -docs = ["Sphinx (>=2.4)", "sphinx-rtd-theme (>=1.2.2)", "urllib3 (<2)"] -test = ["Faker (>=1.0.8)", "allpairspy (>=2)", "click (>=6.2)", "pytest (>=6.0.1)", "pytest-discord (>=0.1.4)", "pytest-md-report (>=0.4.1)"] +docs = ["Sphinx (>=2.4)", "sphinx_rtd_theme (>=1.2.2)", "urllib3 (<2)"] +readme = ["path (>=13,<18)", "readmemaker (>=1.2.0)"] +test = ["Faker (>=1.0.8)", "allpairspy (>=2)", "click (>=6.2)", "pytest (>=6.0.1)", "pytest-md-report (>=0.6.2)"] [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [[package]] -name = "setuptools" -version = "72.1.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, - {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, -] - -[package.extras] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "simfile" -version = "2.1.1" -description = "Modern simfile library for Python" +name = "pyfakefs" +version = "4.5.6" +description = "pyfakefs implements a fake file system that mocks the Python file system modules." optional = false python-versions = ">=3.6" files = [ - {file = "simfile-2.1.1-py3-none-any.whl", hash = "sha256:133094a33cf6b841ca7e620880ef8354c56dd4aef6b2c77582de7f120f69c620"}, - {file = "simfile-2.1.1.tar.gz", hash = "sha256:a7885f8395cdd0f19cd79da34e5675da778aa0e8dda76543cf075e21b862c4b7"}, + {file = "pyfakefs-4.5.6-py3-none-any.whl", hash = "sha256:6bb4e27457b0bc90e3ebfe5aed4f1b8c32a18713ba44e925f304bb9b9816a03c"}, + {file = "pyfakefs-4.5.6.tar.gz", hash = "sha256:914d7bf994406cfbefee0b4d45918f60c15b406afe93f8194a804da5a450a822"}, +] + +[[package]] +name = "setuptools" +version = "75.8.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +files = [ + {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, + {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "simfile" +version = "3.0.0a4" +description = "Simfile parsing & editing library" +optional = false +python-versions = ">=3.10" +files = [ + {file = "simfile-3.0.0a4-py3-none-any.whl", hash = "sha256:dc86236debdecf257065c5614397655503d78e8679c801215b3b3a93bdb2ef9c"}, + {file = "simfile-3.0.0a4.tar.gz", hash = "sha256:c20e5c8aa7833ce48d54e2f42ad068466271209861630f4d2e17a9cc6f0b3460"}, ] [package.dependencies] -fs = ">=2.4.15,<2.5.0" -msdparser = ">=2.0.0,<2.1.0" +fs = {version = "*", optional = true, markers = "extra == \"fs\""} +msdparser = ">=3.0.0a7,<3.1.0" +pyfakefs = "4.5.6" + +[package.extras] +all = ["fs", "pillow"] +assets = ["pillow"] +fs = ["fs"] [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "cb0ec3bd6d2ec3fb7296e6dd4801035c044c88cc2e7da894954d5805603b9fee" +content-hash = "e855e50e2b9a7cbc4e17dbabdd57f17341c367540360e9ae990752eb0b481afa" diff --git a/pyproject.toml b/pyproject.toml index 510eaa3..48c323f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" -simfile = "^2.1.1" +simfile = {version = "^3.0.0a4", extras = ["fs"]} pathvalidate = "^3.2.0" [tool.poetry.group.dev.dependencies]