1
0
mirror of synced 2025-01-31 12:13:48 +01:00

Add marshmallow models for memon

This commit is contained in:
Stepland 2020-06-01 22:20:21 +02:00
parent cbb902de84
commit 3a0110ea07
3 changed files with 127 additions and 14 deletions

View File

@ -8,13 +8,13 @@ parse than existing "memo-like" formats (memo, youbeat, etc ...).
https://github.com/Stepland/memon
"""
import warnings
from path import Path
from typing import Mapping, IO, Iterable, Tuple, Any, Dict, Union, List
from typing import IO, Iterable, Tuple, Any, Dict, Union, List
from io import BytesIO
from itertools import chain
from path import Path
import simplejson as json
from marshmallow import Schema, fields, RAISE, validate, validates_schema, ValidationError
from jubeatools.song import Song, BPMChange, TapNote, LongNote
from jubeatools.utils import lcm
@ -29,7 +29,7 @@ from jubeatools.utils import lcm
# 6
# 10
LONG_NOTE_VALUE_V0 = {
X_Y_OFFSET_TO_P_VALUE = {
(0, -1): 0,
(0, -2): 4,
(0, -3): 8,
@ -44,9 +44,101 @@ LONG_NOTE_VALUE_V0 = {
(-3, 0): 11
}
P_VALUE_TO_X_Y_OFFSET = { v: k for k, v in X_Y_OFFSET_TO_P_VALUE.items() }
class StrictSchema(Schema):
class Meta:
unknown = RAISE
class MemonNote(StrictSchema):
n = fields.Integer(required=True, validate=validate.Range(min=0, max=15))
t = fields.Integer(required=True, validate=validate.Range(min=0))
l = fields.Integer(required=True, validate=validate.Range(min=0))
p = fields.Integer(required=True, validate=validate.Range(min=0, max=11))
@validates_schema
def validate_tail_tip_position(self, data, **kwargs):
if data["l"] > 0:
x = data["n"] % 4
y = data["n"] // 4
dx, dy = P_VALUE_TO_X_Y_OFFSET[data["p"]]
if (not (0 <= x + dx < 4 and 0 <= y + dy < 4)):
raise ValidationError("Invalid tail position : {data}")
class MemonChart_0_1_0(StrictSchema):
level = fields.Integer(required=True)
resolution = fields.Integer(required=True, validate=validate.Range(min=1))
notes = fields.Nested(MemonNote, many=True)
class MemonChart_legacy(MemonChart_0_1_0):
dif_name = fields.String(required=True)
class MemonMetadata_legacy(StrictSchema):
title = fields.String(required=True, data_key="song title")
artist = fields.String(required=True)
audio = fields.String(required=True, data_key="music path")
cover = fields.String(required=True, data_key="jacket path")
BPM = fields.Decimal(required=True, validate=validate.Range(min=0, min_inclusive=False))
offset = fields.Decimal(required=True)
class MemonMetadata_0_1_0(MemonMetadata_legacy):
cover = fields.String(required=True, data_key="album cover path")
class MemonPreview(StrictSchema):
position = fields.Decimal(required=True, validate=validate.Range(min=0))
length = fields.Decimal(required=True, validate=validate.Range(min=0, min_inclusive=False))
class MemonMetadata_0_2_0(MemonMetadata_0_1_0):
preview = fields.Nested(MemonPreview)
class Memon_legacy(StrictSchema):
metadata = fields.Nested(MemonMetadata_legacy, required=True)
data = fields.Nested(MemonChart_legacy, required=True, many=True)
class Memon_0_1_0(StrictSchema):
version = fields.String(required=True, validate=validate.OneOf(["0.1.0"]))
metadata = fields.Nested(MemonMetadata_0_1_0, required=True)
data = fields.Dict(keys=fields.String(), values=MemonChart_0_1_0(), required=True)
class Memon_0_2_0(StrictSchema):
version = fields.String(required=True, validate=validate.OneOf(["0.2.0"]))
metadata = fields.Nested(MemonMetadata_0_2_0, required=True)
data = fields.Dict(keys=fields.String(), values=MemonChart_0_1_0(), required=True)
def _search_and_load(file_or_folder: Path) -> Any:
"""If given a folder, search for a single .memon file then json.load it
If given a file, just json.load it"""
if file_or_folder.isdir():
memon_files = file_or_folder.files("*.memon")
if len(memon_files) > 1:
raise ValueError(f"Multiple memon files found in {file_or_folder}")
elif len(memon_files) == 0:
raise ValueError(f"No memon file found in {file_or_folder}")
file_path = memon_files[0]
else:
file_path = file_or_folder
return json.load(open(file_path), use_decimal=True)
def load_memon_legacy(file_or_folder: Path) -> Song:
...
memon = _search_and_load(file_or_folder)
def load_memon_0_1_0(file_or_folder: Path) -> Song:
@ -61,10 +153,11 @@ def _long_note_tail_value_v0(note: LongNote) -> int:
dx = note.tail_tip.x - note.position.x
dy = note.tail_tip.y - note.position.y
try:
return LONG_NOTE_VALUE_V0[dx, dy]
return X_Y_OFFSET_TO_P_VALUE[dx, dy]
except KeyError:
raise ValueError(f"memon cannot represent a long note with its tail starting ({dx}, {dy}) away from the note") from None
def check_representable_in_v0(song: Song, version: str) -> None:
"""Raises an exception if the Song object is ill-formed or contains information
@ -114,6 +207,7 @@ def _compute_resolution(notes: List[Union[TapNote, LongNote]]) -> int:
def _iter_dump_notes_v0(resolution: int, notes: List[Union[TapNote, LongNote]]) -> Iterable[Dict[str, int]]:
"""Iterable that converts notes into the {n, t, l, p} form"""
for note in sorted(set(notes), key=lambda n: (n.time, n.position)):
memon_note = {
"n": note.index,
@ -132,7 +226,6 @@ def dump_memon_legacy(song: Song) -> Iterable[Tuple[Any, IO]]:
check_representable_in_v0(song, "legacy")
# JSON object preparation
memon = {
"metadata": {
"song title": song.metadata.title,
@ -160,7 +253,6 @@ def dump_memon_0_1_0(song: Song, folder: Path) -> None:
check_representable_in_v0(song, "legacy")
# JSON object preparation
memon = {
"version": "0.1.0",
"metadata": {
@ -188,7 +280,6 @@ def dump_memon_0_2_0(song: Song, folder: Path) -> None:
check_representable_in_v0(song, "legacy")
# JSON object preparation
memon = {
"version": "0.2.0",
"metadata": {
@ -198,13 +289,16 @@ def dump_memon_0_2_0(song: Song, folder: Path) -> None:
"album cover path": str(song.metadata.cover),
"BPM": song.global_timing.events[0].BPM,
"offset": song.global_timing.beat_zero_offset,
"preview" : {
"position": song.metadata.preview_start,
"length": song.metadata.preview_length,
}
},
"data": {}
}
if song.metadata.preview_length != 0:
memon["metadata"]["preview"] = {
"position": song.metadata.preview_start,
"length": song.metadata.preview_length,
}
for difficulty, chart in song.charts.items():
resolution = _compute_resolution(chart.notes)
memon["data"][difficulty] = {

20
poetry.lock generated
View File

@ -66,6 +66,20 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.3"
[[package]]
category = "main"
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
name = "marshmallow"
optional = false
python-versions = ">=3.5"
version = "3.6.0"
[package.extras]
dev = ["pytest", "pytz", "simplejson", "mypy (0.770)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)", "tox"]
docs = ["sphinx (3.0.3)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"]
lint = ["mypy (0.770)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)"]
tests = ["pytest", "pytz", "simplejson"]
[[package]]
category = "dev"
description = "More routines for operating on iterables, beyond itertools"
@ -223,7 +237,7 @@ python-versions = "*"
version = "0.1.9"
[metadata]
content-hash = "4c6754363fc490de2f3c7af8747cd102854de6437c41de5612bc8c1f14cb7cd9"
content-hash = "b55c6b2244d11c0356dc49e32615244f2b964ac0b903d8613a5e0e453557643f"
python-versions = "^3.8"
[metadata.files]
@ -251,6 +265,10 @@ colorama = [
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
]
marshmallow = [
{file = "marshmallow-3.6.0-py2.py3-none-any.whl", hash = "sha256:f88fe96434b1f0f476d54224d59333eba8ca1a203a2695683c1855675c4049a7"},
{file = "marshmallow-3.6.0.tar.gz", hash = "sha256:c2673233aa21dde264b84349dc2fd1dce5f30ed724a0a00e75426734de5b84ab"},
]
more-itertools = [
{file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"},
{file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"},

View File

@ -11,6 +11,7 @@ multidict = "^4.7.6"
click = "^7.1.2"
path = "^14.0.1"
simplejson = "^3.17.0"
marshmallow = "^3.6.0"
[tool.poetry.dev-dependencies]
pytest = "^5.2"