Initial commit
This commit is contained in:
commit
34415a8062
5 changed files with 451 additions and 0 deletions
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"python.pythonPath": ".venv/bin/python"
|
||||||
|
}
|
86
change_sync_bias.py
Normal file
86
change_sync_bias.py
Normal 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
229
poetry.lock
generated
Normal 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
115
prefix_title_with_meter.py
Normal 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
18
pyproject.toml
Normal 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"
|
Loading…
Reference in a new issue