many, many changes. why wasn't i committing these

This commit is contained in:
Ash Garcia 2025-07-22 14:49:28 -07:00
parent 38a486131d
commit 535f53c55a
5 changed files with 414 additions and 129 deletions

2
.gitignore vendored
View file

@ -1,2 +1,2 @@
testdata/ testdata/
output/ packs/

View file

@ -1,3 +1,10 @@
========== Banned alias positions ==========
* The
* #090
* もしン
* CAR????
* Non
東京 *
========== (R2) AGEN WIDA ========== ========== (R2) AGEN WIDA ==========
Angry Squid Angry Squid
Blonde Fox Blonde Fox
@ -658,3 +665,168 @@ Flavorless Boys
Bullet Refutal Bullet Refutal
Vinyl Countdown Vinyl Countdown
Blue Vocals 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

View file

@ -18,14 +18,16 @@ from fs.base import FS
from fs.copy import copy_dir from fs.copy import copy_dir
from fs.tempfs import TempFS from fs.tempfs import TempFS
from fs.zipfs import ZipFS from fs.zipfs import ZipFS
from msdparser import MSDParserError
from pathvalidate import sanitize_filename from pathvalidate import sanitize_filename
import simfile import simfile
from simfile.dir import SimfilePack, SimfileDirectory from simfile.dir import SimfilePack, SimfileDirectory
from simfile.notes import NoteData from simfile.notes import NoteData
from simfile.sm import SMChart, SMSimfile from simfile.sm import SMChart, SMSimfile
from simfile.ssc import SSCChart, SSCSimfile from simfile.ssc import SSCChart, SSCSimfile
from simfile.timing import BeatValues, BeatValue from simfile.tidy import tidy, Preset, RemoveComments
from simfile.types import Simfile, Chart 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): class KnownColumns(enum.StrEnum):
Timestamp = "Timestamp" Timestamp = "Timestamp"
UserId = "Your gamer tag/alias: (e.g. dimo)" UserId = "Your gamer tag: (e.g. dimo)"
GeneratedAlias = "Generated Alias" GeneratedAlias = "Generated Alias"
IgnoreFile = "Ignore File" IgnoreFile = "Ignore File"
SongTitle = "Song Title"
SongArtist = "Song Artist"
# Not persisted: # Not persisted:
ExtractedTo = "Extracted To" ExtractedTo = "Extracted To"
@ -159,11 +163,32 @@ ChangedCsvContents = bool
def parse_timestamp(timestamp: str): 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: 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: for row in csv_contents:
rnd = Random(",".join([row[KnownColumns.UserId], seed])) rnd = Random(",".join([row[KnownColumns.UserId], seed]))
while True: 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 ( if (
random_alias in alias_to_user_id random_alias in alias_to_user_id
and alias_to_user_id[random_alias] != row[KnownColumns.UserId] and alias_to_user_id[random_alias] != row[KnownColumns.UserId]
@ -288,7 +318,11 @@ def maybe_generate_aliases(
print( print(
f"Rerolling alias for {row[KnownColumns.UserId]} because {repr(random_alias)} is already being used by {alias_to_user_id[random_alias]}" 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( print(
f"Rerolling alias for {row[KnownColumns.UserId]} because {repr(random_alias)} has already been used" 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: def extract_simfile_dir(zip_fs: FS, temp_fs: FS) -> str:
zip_path, simfile_dir = find_simfile_dir_zip_path(zip_fs) zip_path, simfile_dir = find_simfile_dir_zip_path(zip_fs)
try:
canonical_filename = canonical_simfile_filename(simfile_dir.open()) canonical_filename = canonical_simfile_filename(simfile_dir.open())
except MSDParserError:
print("Exception encountered while processing", simfile_dir)
raise
assert not temp_fs.exists( assert not temp_fs.exists(
canonical_filename 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) copy_dir(zip_fs, zip_path, temp_fs, canonical_filename)
return canonical_filename return canonical_filename
@ -455,7 +493,7 @@ def maybe_anonymize_entries(
) )
os.rename(absolute_path, absolute_canonical_path) os.rename(absolute_path, absolute_canonical_path)
print( 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): def maybe_delete_file(absolute_path: str | None):
@ -463,19 +501,24 @@ def maybe_anonymize_entries(
os.remove(absolute_path) os.remove(absolute_path)
print(f"Deleted {os.path.relpath(absolute_path, temp_fs.root_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) 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( bpm_values.append(
BeatValue( BeatValue(
beat=bpm_values[-1].beat + 10000, beat=last_beat + 4,
value=bpm_values[-1].value + Decimal("0.001"), 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))}") print(f"Anonymized BPMs from {repr(bpm_str)} to {repr(str(bpm_values))}")
return str(bpm_values)
def clean_up_difficulties(sf: Simfile): def clean_up_difficulties(sf: Simfile) -> Chart:
charts_to_remove: list[Chart] = [] charts_to_remove: list[AttachedChart] = []
chart_with_notes = None chart_with_notes = None
for _chart in sf.charts: for _chart in sf.charts:
@ -505,7 +548,45 @@ def maybe_anonymize_entries(
print( print(
f"WARNING: removing {chart_to_remove.difficulty} chart with no notes from {canonical_filename}" 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: for row in csv_contents:
if row[KnownColumns.IgnoreFile]: if row[KnownColumns.IgnoreFile]:
@ -529,18 +610,7 @@ def maybe_anonymize_entries(
if simfile_dir.sm_path: if simfile_dir.sm_path:
with simfile.mutate(simfile_dir.sm_path) as sm: with simfile.mutate(simfile_dir.sm_path) as sm:
assert isinstance(sm, SMSimfile) assert isinstance(sm, SMSimfile)
sm.credit = row[KnownColumns.GeneratedAlias] anonymize_simfile(sm)
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]
maybe_rename_file(simfile_dir.sm_path, f"{canonical_filename}.sm") maybe_rename_file(simfile_dir.sm_path, f"{canonical_filename}.sm")
print( print(
f"Scrubbed {os.path.relpath(absolute_simfile_dir_path, temp_fs.root_path)}/{canonical_filename}.sm" 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: if simfile_dir.ssc_path:
with simfile.mutate(simfile_dir.ssc_path) as ssc: with simfile.mutate(simfile_dir.ssc_path) as ssc:
assert isinstance(ssc, SSCSimfile) assert isinstance(ssc, SSCSimfile)
ssc.credit = row[KnownColumns.GeneratedAlias] anonymize_simfile(ssc)
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)
maybe_rename_file(simfile_dir.ssc_path, f"{canonical_filename}.ssc") maybe_rename_file(simfile_dir.ssc_path, f"{canonical_filename}.ssc")
print( print(
f"Scrubbed {os.path.relpath(absolute_simfile_dir_path, temp_fs.root_path)}/{canonical_filename}.ssc" 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(".txt")
or dir_entry.name.endswith(".zip") or dir_entry.name.endswith(".zip")
or dir_entry.name == ".DS_Store" or dir_entry.name == ".DS_Store"
or dir_entry.name == "desktop.ini"
): ):
# These are definitely safe to delete for distribution # These are definitely safe to delete for distribution
os.remove(dir_entry.path) os.remove(dir_entry.path)
@ -616,7 +668,7 @@ def maybe_save_anonymized_files(
args: AnonymizeEntriesArgs, args: AnonymizeEntriesArgs,
csv_contents: CsvContents, csv_contents: CsvContents,
temp_fs: TempFS, temp_fs: TempFS,
): ) -> str | None:
if args.dry_run: if args.dry_run:
print("Dry run - not saving files") print("Dry run - not saving files")
return return
@ -625,6 +677,39 @@ def maybe_save_anonymized_files(
output_path = f"{args.output}/{de}anonymized-{timestamp}" output_path = f"{args.output}/{de}anonymized-{timestamp}"
shutil.copytree(temp_fs.root_path, output_path) shutil.copytree(temp_fs.root_path, output_path)
print(f"Saved to {os.path.abspath(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) temp_fs = extract_entries_to_temporary_folder(args, csv_contents, dynamic_columns)
maybe_anonymize_entries(args, csv_contents, temp_fs) 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__": if __name__ == "__main__":

174
poetry.lock generated
View file

@ -13,33 +13,33 @@ files = [
[[package]] [[package]]
name = "black" name = "black"
version = "24.8.0" version = "24.10.0"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.9"
files = [ files = [
{file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
{file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
{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.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"},
{file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"},
{file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"},
{file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"},
{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.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"},
{file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"},
{file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"},
{file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"},
{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.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"},
{file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"},
{file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"},
{file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"},
{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.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"},
{file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"},
{file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"},
{file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"},
{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.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"},
{file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"},
{file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"},
{file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"},
] ]
[package.dependencies] [package.dependencies]
@ -51,19 +51,19 @@ platformdirs = ">=2"
[package.extras] [package.extras]
colorama = ["colorama (>=0.4.3)"] 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)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"] uvloop = ["uvloop (>=0.15.2)"]
[[package]] [[package]]
name = "click" name = "click"
version = "8.1.7" version = "8.1.8"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
] ]
[package.dependencies] [package.dependencies]
@ -101,13 +101,13 @@ scandir = ["scandir (>=1.5,<2.0)"]
[[package]] [[package]]
name = "msdparser" name = "msdparser"
version = "2.0.0" version = "3.0.0a7"
description = "Robust & lightning fast MSD parser (StepMania file format)" description = "Low-level parser for StepMania MSD files (SM, SSC, DWI...)"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.10"
files = [ files = [
{file = "msdparser-2.0.0-py3-none-any.whl", hash = "sha256:bbef8c89b2dc16183ba714354b093fff11a43469971c42972284c5046826002d"}, {file = "msdparser-3.0.0a7-py3-none-any.whl", hash = "sha256:ef324aa943e325bc1c1ef6b363bf55433def3deb8a9f2fa608b14eff828ccf64"},
{file = "msdparser-2.0.0.tar.gz", hash = "sha256:9bb6cf4b705c76850b1ce22823c768b62ba2bb63d1c2407bbe3e2c23e38be8e3"}, {file = "msdparser-3.0.0a7.tar.gz", hash = "sha256:797bc6a99ae491cf2712483d2e0fb086ac4e047354cc8f8e4712bce95833c6c6"},
] ]
[[package]] [[package]]
@ -123,13 +123,13 @@ files = [
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.1" version = "24.2"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
] ]
[[package]] [[package]]
@ -145,78 +145,100 @@ files = [
[[package]] [[package]]
name = "pathvalidate" 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." description = "pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.9"
files = [ files = [
{file = "pathvalidate-3.2.0-py3-none-any.whl", hash = "sha256:cc593caa6299b22b37f228148257997e2fa850eea2daf7e4cc9205cef6908dee"}, {file = "pathvalidate-3.2.3-py3-none-any.whl", hash = "sha256:5eaf0562e345d4b6d0c0239d0f690c3bd84d2a9a3c4c73b99ea667401b27bee1"},
{file = "pathvalidate-3.2.0.tar.gz", hash = "sha256:5e8378cf6712bff67fbe7a8307d99fa8c1a0cb28aa477056f8fc374f0dff24ad"}, {file = "pathvalidate-3.2.3.tar.gz", hash = "sha256:59b5b9278e30382d6d213497623043ebe63f10e29055be4419a9c04c721739cb"},
] ]
[package.extras] [package.extras]
docs = ["Sphinx (>=2.4)", "sphinx-rtd-theme (>=1.2.2)", "urllib3 (<2)"] 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)"] 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]] [[package]]
name = "platformdirs" 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`." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
{file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
] ]
[package.extras] [package.extras]
docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] 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 (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
type = ["mypy (>=1.8)"] type = ["mypy (>=1.11.2)"]
[[package]] [[package]]
name = "setuptools" name = "pyfakefs"
version = "72.1.0" version = "4.5.6"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "pyfakefs implements a fake file system that mocks the Python file system modules."
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"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
{file = "simfile-2.1.1-py3-none-any.whl", hash = "sha256:133094a33cf6b841ca7e620880ef8354c56dd4aef6b2c77582de7f120f69c620"}, {file = "pyfakefs-4.5.6-py3-none-any.whl", hash = "sha256:6bb4e27457b0bc90e3ebfe5aed4f1b8c32a18713ba44e925f304bb9b9816a03c"},
{file = "simfile-2.1.1.tar.gz", hash = "sha256:a7885f8395cdd0f19cd79da34e5675da778aa0e8dda76543cf075e21b862c4b7"}, {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] [package.dependencies]
fs = ">=2.4.15,<2.5.0" fs = {version = "*", optional = true, markers = "extra == \"fs\""}
msdparser = ">=2.0.0,<2.1.0" msdparser = ">=3.0.0a7,<3.1.0"
pyfakefs = "4.5.6"
[package.extras]
all = ["fs", "pillow"]
assets = ["pillow"]
fs = ["fs"]
[[package]] [[package]]
name = "six" name = "six"
version = "1.16.0" version = "1.17.0"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [ files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
] ]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.11" python-versions = "^3.11"
content-hash = "cb0ec3bd6d2ec3fb7296e6dd4801035c044c88cc2e7da894954d5805603b9fee" content-hash = "e855e50e2b9a7cbc4e17dbabdd57f17341c367540360e9ae990752eb0b481afa"

View file

@ -7,7 +7,7 @@ readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.11" python = "^3.11"
simfile = "^2.1.1" simfile = {version = "^3.0.0a4", extras = ["fs"]}
pathvalidate = "^3.2.0" pathvalidate = "^3.2.0"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]