Initial commit

This commit is contained in:
ashastral 2022-07-28 21:15:00 -07:00
commit 34415a8062
5 changed files with 451 additions and 0 deletions

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"python.pythonPath": ".venv/bin/python"
}

86
change_sync_bias.py Normal file
View file

@ -0,0 +1,86 @@
import argparse
from decimal import Decimal
import sys
from typing import Union
import simfile
from simfile.dir import SimfilePack
class ChangeSyncBiasArgs:
"""Stores the command-line arguments for this script."""
pack: str
itg_to_null: bool
null_to_itg: bool
def argparser():
"""Get an ArgumentParser instance for this command-line script."""
parser = argparse.ArgumentParser(prefix_chars="-+")
parser.add_argument("pack", type=str, help="path to the pack to modify")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"-9", "--itg-to-null", action="store_true", help="subtract 9ms from offsets"
)
group.add_argument(
"+9", "--null-to-itg", action="store_true", help="add 9ms to offsets"
)
return parser
def adjust_offset(
obj: Union[simfile.types.Simfile, simfile.ssc.SSCChart],
delta: Decimal,
):
"""Add the delta to the simfile or SSC chart's offset, if present."""
if obj.offset is not None:
obj.offset = str(Decimal(obj.offset) + delta)
def change_sync_bias(simfile_path: str, args: ChangeSyncBiasArgs):
"""
Add or subtract 9 milliseconds to the simfile's offset,
as well as any SSC charts with their own timing data.
This saves the updated simfile to its original location
and writes a backup copy with a ~ appended to the filename.
"""
# Map the +9 or -9 arg to the actual offset delta.
#
# We don't have to check both itg_to_null and null_to_itg
# because the mutually exclusive & required argument group
# ensures that exactly one of them will be True.
delta = Decimal("-0.009" if args.itg_to_null else "+0.009")
# 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:
# Always adjust the simfile's offset
adjust_offset(sf, delta)
# Additionally try to adjust SSC charts' offsets.
# This won't do anything unless the chart has its own timing data.
if isinstance(sf, simfile.ssc.SSCSimfile):
for chart in sf.charts:
adjust_offset(chart, delta)
def main(argv):
# Parse command-line arguments
args = argparser().parse_args(argv[1:], namespace=ChangeSyncBiasArgs())
# Iterate over SimfileDirectory objects from the pack
# so that we can easily get the .sm and/or .ssc paths
for simfile_dir in SimfilePack(args.pack).simfile_dirs():
# Try to update whichever formats exist
for simfile_path in [simfile_dir.sm_path, simfile_dir.ssc_path]:
if simfile_path:
change_sync_bias(simfile_path, args)
if __name__ == "__main__":
main(sys.argv)

229
poetry.lock generated Normal file
View file

@ -0,0 +1,229 @@
[[package]]
name = "appdirs"
version = "1.4.4"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "black"
version = "22.6.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6.2"
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "colorama"
version = "0.4.5"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "fs"
version = "2.4.16"
description = "Python's filesystem abstraction layer"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
appdirs = ">=1.4.3,<1.5.0"
six = ">=1.10,<2.0"
[package.extras]
scandir = ["scandir (>=1.5,<2.0)"]
[[package]]
name = "importlib-metadata"
version = "4.12.0"
description = "Read metadata from Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
perf = ["ipython"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
[[package]]
name = "msdparser"
version = "2.0.0"
description = "Robust & lightning fast MSD parser (StepMania file format)"
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "mypy"
version = "0.971"
description = "Optional static typing for Python"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
mypy-extensions = ">=0.4.3"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
typing-extensions = ">=3.10"
[package.extras]
dmypy = ["psutil (>=4.0)"]
python2 = ["typed-ast (>=1.4.0,<2)"]
reports = ["lxml"]
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "pathspec"
version = "0.9.0"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[[package]]
name = "platformdirs"
version = "2.5.2"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
[[package]]
name = "simfile"
version = "2.1.0rc1"
description = "Modern simfile library for Python"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
fs = ">=2.4.15,<2.5.0"
msdparser = ">=2.0.0b4,<2.1.0"
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "typed-ast"
version = "1.5.4"
description = "a fork of Python 2 and 3 ast modules with type comment support"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "typing-extensions"
version = "4.3.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "zipp"
version = "3.8.1"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "a68c45fcf34f1ce3e8acd3562ff47bed859d19846aed1c332c4576574c9e5301"
[metadata.files]
appdirs = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
black = []
click = []
colorama = []
fs = [
{file = "fs-2.4.16-py2.py3-none-any.whl", hash = "sha256:660064febbccda264ae0b6bace80a8d1be9e089e0a5eb2427b7d517f9a91545c"},
{file = "fs-2.4.16.tar.gz", hash = "sha256:ae97c7d51213f4b70b6a958292530289090de3a7e15841e108fbe144f069d313"},
]
importlib-metadata = []
msdparser = []
mypy = []
mypy-extensions = []
pathspec = []
platformdirs = []
simfile = []
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
tomli = []
typed-ast = []
typing-extensions = [
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
]
zipp = []

115
prefix_title_with_meter.py Normal file
View file

@ -0,0 +1,115 @@
import argparse
import sys
from typing import Optional, Sequence
import simfile
from simfile.dir import SimfilePack
class PrefixTitleWithMeterArgs:
"""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,
)
def prefix_title_with_meter(simfile_path: str, args: PrefixTitleWithMeterArgs):
"""
Add (or remove) a prefix to the simfile's title.
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:
# 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
args = argparser().parse_args(argv[1:], namespace=PrefixTitleWithMeterArgs())
# Iterate over SimfileDirectory objects from the pack
# so that we can easily get the .sm and/or .ssc paths
for simfile_dir in SimfilePack(args.pack).simfile_dirs():
# 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)

18
pyproject.toml Normal file
View file

@ -0,0 +1,18 @@
[tool.poetry]
name = "simfile-scripts"
version = "0.1.0"
description = ""
authors = ["ash garcia <github@garcia.sh>"]
[tool.poetry.dependencies]
python = "^3.7"
simfile = "^2.1.0rc1"
msdparser = "^2.0.0"
[tool.poetry.dev-dependencies]
mypy = "^0.971"
black = "^22.6.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"