aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore174
-rw-r--r--README.md1
-rw-r--r--chuni/chuni_aquadx_to_tachi.py102
-rw-r--r--sdvx/sdvx_csv_to_tachi.py101
4 files changed, 378 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1800114
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,174 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# UV
+# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+#uv.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+# Ruff stuff:
+.ruff_cache/
+
+# PyPI configuration file
+.pypirc \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..19ae51a
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+Some scripts to convert from various sources to Tachi/Kamaitachi "Batch Manual" import jsons \ No newline at end of file
diff --git a/chuni/chuni_aquadx_to_tachi.py b/chuni/chuni_aquadx_to_tachi.py
new file mode 100644
index 0000000..4063ac2
--- /dev/null
+++ b/chuni/chuni_aquadx_to_tachi.py
@@ -0,0 +1,102 @@
+import argparse
+import json
+
+DIFFICULTY_MAPPING = {
+ 0: "BASIC",
+ 1: "ADVANCED",
+ 2: "EXPERT",
+ 3: "MASTER",
+ 4: "ULTIMA",
+ 5: "WORLD'S END",
+}
+
+def convert_chuni_aquadx_json_to_tachi_json(input_json: str, output_file: str, service: str):
+ with open(input_json, "r", encoding="utf-8") as f:
+ raw_data = json.load(f)
+
+ batch_manual = {
+ "meta": {"game": "chunithm", "playtype": "Single", "service": service},
+ "scores": [],
+ }
+
+ processed_count = 0
+ skipped_count = 0
+
+ if "userPlaylogList" in raw_data:
+ for entry in raw_data["userPlaylogList"]:
+ level = entry.get("level", 0)
+
+ # Skip World's End, Unsupported by Tachi
+ if level == 5 or level not in DIFFICULTY_MAPPING:
+ skipped_count += 1
+ continue
+
+ processed_count += 1
+ music_id = entry["musicId"]
+
+ score_value = entry.get("score", 0)
+ is_clear = entry.get("isClear", False)
+ is_full_combo = entry.get("isFullCombo", False)
+ is_all_justice = entry.get("isAllJustice", False)
+ is_all_perfect = entry.get("isAllPerfect", False)
+ lamp = "FAILED"
+ if is_all_perfect:
+ lamp = "ALL JUSTICE CRITICAL"
+ elif is_all_justice:
+ lamp = "ALL JUSTICE"
+ elif is_full_combo:
+ lamp = "FULL COMBO"
+ elif is_clear:
+ lamp = "CLEAR"
+ timestamp = entry.get("sortNumber", None)
+
+ jcrit = entry.get("judgeHeaven", 0) + entry.get("judgeCritical", 0)
+ justice = entry.get("judgeJustice", 0)
+ attack = entry.get("judgeAttack", 0)
+ miss = entry.get("judgeGuilty", 0)
+ combo = entry.get("maxCombo", 0)
+
+ score_entry = {
+ "score": score_value,
+ "lamp": lamp,
+ "matchType": "inGameID",
+ "identifier": str(music_id),
+ "difficulty": DIFFICULTY_MAPPING[level],
+ "timeAchieved": timestamp * 1000 if timestamp else None,
+ "judgements": {
+ "jcrit": jcrit,
+ "justice": justice,
+ "attack": attack,
+ "miss": miss,
+ },
+ "optional": {"maxCombo": combo},
+ }
+
+ batch_manual["scores"].append(score_entry)
+
+ with open(output_file, "w", encoding="utf-8") as f:
+ print("--- Processing Summary ---")
+ print(f"Total scores processed: {processed_count}")
+ print(f"Scores skipped (level 5 or invalid): {skipped_count}")
+ print(f"Output saved to {output_file}")
+ json.dump(batch_manual, f, indent=4, ensure_ascii=False)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ prog="chuni_aquadx_to_tachi",
+ description="Converts AquaDX score data for Chuni to Tachi compatible JSON",
+ epilog="Fast/Slow can't be derived (I think)",
+ )
+ parser.add_argument("input_file", help="Path to the input JSON file exported from AquaDX")
+ parser.add_argument(
+ "-s",
+ "--service",
+ help="Service description to be shown on Tachi (Note for where this score came from)",
+ default="AquaDX Chuni Import",
+ )
+ parser.add_argument(
+ "-o", "--output", help="Output filename", default="aquadx_chuni_tachi.json"
+ )
+ args = parser.parse_args()
+ convert_chuni_aquadx_json_to_tachi_json(args.input_file, args.output, args.service)
diff --git a/sdvx/sdvx_csv_to_tachi.py b/sdvx/sdvx_csv_to_tachi.py
new file mode 100644
index 0000000..062b0f4
--- /dev/null
+++ b/sdvx/sdvx_csv_to_tachi.py
@@ -0,0 +1,101 @@
+import csv
+import json
+import argparse
+
+DIFFICULTY_MAPPING = {
+ "NOVICE": "NOV",
+ "ADVANCED": "ADV",
+ "EXHAUST": "EXH",
+ "INFINITE": "INF",
+ "GRAVITY": "GRV",
+ "HEAVENLY": "HVN",
+ "VIVD": "VVD",
+ "EXCEED": "EXCEED",
+ "MAXIMUM": "MXM"
+}
+
+LAMP_MAPPING = {
+ "PLAYED": "FAILED",
+ "COMPLETE": "CLEAR",
+}
+
+def convert_sdvx_csv_to_tachi_json(csv_file, game, playtype, service):
+ encodings = ['utf-8-sig', 'utf-8', 'shift-jis', 'cp932']
+
+ for encoding in encodings:
+ try:
+ batch_manual = {
+ "meta": {
+ "game": game,
+ "playtype": playtype,
+ "service": service
+ },
+ "scores": []
+ }
+
+ with open(csv_file, newline='', encoding=encoding) as f:
+ reader = csv.DictReader(f)
+ fieldnames = reader.fieldnames
+ required_fields = ["楽曲名", "難易度", "クリアランク", "ハイスコア"]
+ if not all(field in fieldnames for field in required_fields):
+ continue
+
+
+ for row in reader:
+ lamp = LAMP_MAPPING[row["クリアランク"].upper()]
+ if row.get("ULTIMATE CHAIN"):
+ lamp = "ULTIMATE CHAIN"
+ if row.get("PERFECT"):
+ lamp = "PERFECT ULTIMATE CHAIN"
+
+ score_entry = {
+ "score": int(row["ハイスコア"]),
+ "lamp": lamp,
+ "matchType": "songTitle",
+ "identifier": row["楽曲名"],
+ "difficulty": DIFFICULTY_MAPPING[row["難易度"].upper()],
+ }
+ optional_fields = {}
+ if row.get("EXスコア"):
+ optional_fields["exScore"] = int(row["EXスコア"])
+ if row.get("fast"):
+ optional_fields["fast"] = int(row["fast"])
+ if row.get("slow"):
+ optional_fields["slow"] = int(row["slow"])
+ if row.get("maxCombo"):
+ optional_fields["maxCombo"] = int(row["maxCombo"])
+ if row.get("gauge"):
+ optional_fields["gauge"] = float(row["gauge"])
+ if optional_fields:
+ score_entry["optional"] = optional_fields
+ batch_manual["scores"].append(score_entry)
+ return batch_manual
+ except UnicodeDecodeError:
+ continue
+ except Exception as e:
+ print(f"Error with encoding {encoding}: {str(e)}")
+ continue
+
+ raise ValueError("Failed to read CSV file with any supported encoding")
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ prog="sdvx_csv_to_tachi",
+ description="Converts CSV data exported from SDVX eAmuse site to Tachi compatibile JSON",
+ epilog="Note that not all information can be derived from the CSV so some fields will be missing from Tachi"
+ )
+ parser.add_argument("csv_filename", help="Path to the CSV file")
+ parser.add_argument("-s", "--service", help="Service description to be shown on Tachi (Note for where this score came from)", default="SDVX Arcade Import")
+ parser.add_argument("-o", "--output", help="Output filename", default="sdvx_tachi.json")
+ args = parser.parse_args()
+
+try:
+ output_json = convert_sdvx_csv_to_tachi_json(args.csv_filename, "sdvx", "Single", args.service)
+
+ with open(args.output, "w", encoding="utf-8") as json_file:
+ json.dump(output_json, json_file, ensure_ascii=False, indent=4)
+
+ print("Conversion completed. JSON saved as " + args.output)
+except Exception as e:
+ print(f"Error: {str(e)}")
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage