commit
587e68ab3e
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,10 +1,20 @@
|
|||||||
# v1.4.0
|
# v1.4.0
|
||||||
## Added
|
## Added
|
||||||
- Jubeatools can now handle HAKUs, in the following formats :
|
- Jubeatools can now handle HAKUs in the following formats :
|
||||||
- [memon:v1.0.0]
|
- [memon:v1.0.0]
|
||||||
- [memon] 🎉 inital support for v1.0.0 !
|
- [eve]
|
||||||
|
- [jbsq]
|
||||||
|
- [memon]
|
||||||
|
- 🎉 inital support for v1.0.0 !
|
||||||
|
- `--merge` option allows for several memon files to be merged when
|
||||||
|
jubeatools is called on a folder
|
||||||
## Changed
|
## Changed
|
||||||
- Improved the merging procedure for song objects
|
- Improved the merging procedure for song objects
|
||||||
|
- Re-enable calling the CLI on a folder, this was disabled for some reason ?
|
||||||
|
- The song class now uses a regular dict to map difficuty names to chart
|
||||||
|
objects, dissalowing files with duplicate difficulties (`memon:legacy` was the
|
||||||
|
only format that *technically* supported this anyway, I conscider it an edge
|
||||||
|
case not really worth handling)
|
||||||
|
|
||||||
# v1.3.0
|
# v1.3.0
|
||||||
## Added
|
## Added
|
||||||
|
@ -5,21 +5,23 @@ from typing import Any, Dict, Optional
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from jubeatools.formats import DUMPERS, LOADERS
|
from jubeatools.formats import DUMPERS, LOADERS, Format
|
||||||
from jubeatools.formats.enum import Format
|
|
||||||
from jubeatools.formats.guess import guess_format
|
from jubeatools.formats.guess import guess_format
|
||||||
|
|
||||||
from .helpers import dumper_option, loader_option
|
from .helpers import dumper_option, loader_option
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument("src", type=click.Path(exists=True, dir_okay=False))
|
@click.argument("src", type=click.Path(exists=True, dir_okay=True))
|
||||||
@click.argument("dst", type=click.Path())
|
@click.argument("dst", type=click.Path())
|
||||||
@click.option(
|
@click.option(
|
||||||
"--input-format",
|
"--input-format",
|
||||||
"input_format",
|
"input_format",
|
||||||
type=click.Choice(list(f._value_ for f in LOADERS.keys())),
|
type=click.Choice(list(f._value_ for f in LOADERS.keys())),
|
||||||
help="Input file format",
|
help=(
|
||||||
|
"Force jubeatools to read the input file/folder as the given format."
|
||||||
|
"If this option is not used jubeatools will try to guess the format"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-f",
|
"-f",
|
||||||
@ -45,6 +47,11 @@ from .helpers import dumper_option, loader_option
|
|||||||
"the nearest 1/beat_snap beat"
|
"the nearest 1/beat_snap beat"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@loader_option(
|
||||||
|
"--merge",
|
||||||
|
is_flag=True,
|
||||||
|
help="For memon, if called on a folder, merge all the .memon files found",
|
||||||
|
)
|
||||||
def convert(
|
def convert(
|
||||||
src: str,
|
src: str,
|
||||||
dst: str,
|
dst: str,
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"ADV": {
|
||||||
|
"level": 5,
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"l": 0,
|
||||||
|
"n": 1,
|
||||||
|
"p": 0,
|
||||||
|
"t": 1680
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resolution": 240
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"BPM": 180.28199768066406,
|
||||||
|
"album cover path": "2a03puritans.png",
|
||||||
|
"artist": "commandycan",
|
||||||
|
"music path": "Sky Bus For Hire.ogg",
|
||||||
|
"offset": -0.028,
|
||||||
|
"song title": "Sky Bus For Hire"
|
||||||
|
},
|
||||||
|
"version": "0.1.0"
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"BSC": {
|
||||||
|
"level": 1,
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"l": 0,
|
||||||
|
"n": 2,
|
||||||
|
"p": 0,
|
||||||
|
"t": 1680
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resolution": 240
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"BPM": 180.28199768066406,
|
||||||
|
"album cover path": "2a03puritans.png",
|
||||||
|
"artist": "commandycan",
|
||||||
|
"music path": "Sky Bus For Hire.ogg",
|
||||||
|
"offset": -0.028,
|
||||||
|
"song title": "Sky Bus For Hire"
|
||||||
|
},
|
||||||
|
"version": "0.1.0"
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"EXT": {
|
||||||
|
"level": 10,
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"l": 0,
|
||||||
|
"n": 15,
|
||||||
|
"p": 0,
|
||||||
|
"t": 1680
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resolution": 240
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"BPM": 180.28199768066406,
|
||||||
|
"album cover path": "2a03puritans.png",
|
||||||
|
"artist": "commandycan",
|
||||||
|
"music path": "Sky Bus For Hire.ogg",
|
||||||
|
"offset": -0.028,
|
||||||
|
"song title": "Sky Bus For Hire"
|
||||||
|
},
|
||||||
|
"version": "0.1.0"
|
||||||
|
}
|
2
jubeatools/cli/tests/data/memon_merge/__init__.py
Normal file
2
jubeatools/cli/tests/data/memon_merge/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"""This file is here so the test code can use importlib as a portable way to
|
||||||
|
open test data in this folder"""
|
@ -1,15 +1,17 @@
|
|||||||
from importlib import resources
|
from importlib import resources
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from jubeatools import song as jbt
|
||||||
|
from jubeatools.formats import LOADERS, Format
|
||||||
|
|
||||||
from ..cli import convert
|
from ..cli import convert
|
||||||
from . import data
|
from . import data
|
||||||
|
|
||||||
|
|
||||||
def test_that_ommiting_beat_snap_works() -> None:
|
def test_that_ommiting_beat_snap_works() -> None:
|
||||||
"""
|
"""As pointed out by https://github.com/Stepland/jubeatools/issues/17"""
|
||||||
As pointed out by https://github.com/Stepland/jubeatools/issues/17
|
|
||||||
"""
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
with runner.isolated_filesystem(), resources.path(
|
with runner.isolated_filesystem(), resources.path(
|
||||||
data, "Life Without You.eve"
|
data, "Life Without You.eve"
|
||||||
@ -20,3 +22,50 @@ def test_that_ommiting_beat_snap_works() -> None:
|
|||||||
if result.exception:
|
if result.exception:
|
||||||
raise result.exception
|
raise result.exception
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_that_is_flag_works_the_way_intended() -> None:
|
||||||
|
"""It's unclear to me what the default value is for an option with
|
||||||
|
is_flag=True"""
|
||||||
|
with resources.path(data, "Life Without You.eve") as p:
|
||||||
|
called_with_the_flag = convert.make_context(
|
||||||
|
"convert",
|
||||||
|
[str(p.resolve(strict=True)), "out.txt", "-f", "memo2", "--circlefree"],
|
||||||
|
)
|
||||||
|
assert called_with_the_flag.params["dumper_options"]["circle_free"] is True
|
||||||
|
|
||||||
|
called_without_the_flag = convert.make_context(
|
||||||
|
"convert", [str(p.resolve(strict=True)), "out.txt", "-f", "memo2"]
|
||||||
|
)
|
||||||
|
dumper_options = called_without_the_flag.params.get("dumper_options")
|
||||||
|
if dumper_options is not None:
|
||||||
|
circle_free = dumper_options.get("circle_free")
|
||||||
|
assert not circle_free
|
||||||
|
|
||||||
|
|
||||||
|
def test_that_the_merge_option_works_for_memon_files() -> None:
|
||||||
|
runner = CliRunner()
|
||||||
|
with runner.isolated_filesystem(), resources.path(data, "memon_merge") as p:
|
||||||
|
result = runner.invoke(
|
||||||
|
convert,
|
||||||
|
[
|
||||||
|
"--input-format",
|
||||||
|
"memon:v0.1.0",
|
||||||
|
str(p.resolve(strict=True)),
|
||||||
|
"--merge",
|
||||||
|
"out.memon",
|
||||||
|
"-f",
|
||||||
|
"memon:v0.1.0",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
if result.exception:
|
||||||
|
raise result.exception
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
memon_loader = LOADERS[Format.MEMON_0_1_0]
|
||||||
|
bsc = memon_loader(p / "Sky Bus For Hire BSC.memon")
|
||||||
|
adv = memon_loader(p / "Sky Bus For Hire ADV.memon")
|
||||||
|
ext = memon_loader(p / "Sky Bus For Hire EXT.memon")
|
||||||
|
merged_by_cli = LOADERS[Format.MEMON_0_1_0](Path("out.memon"))
|
||||||
|
merged_with_python = jbt.Song.from_monochart_instances(bsc, adv, ext)
|
||||||
|
assert merged_by_cli == merged_with_python
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Module containing all the load/dump code for all file formats
|
Module containing all the load/dump code for all file formats
|
||||||
"""
|
"""
|
||||||
from .enum import Format
|
from .format_names import Format
|
||||||
from .loaders_and_dumpers import DUMPERS, LOADERS
|
from .loaders_and_dumpers import DUMPERS, LOADERS
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class Format(str, Enum):
|
|
||||||
EVE = "eve"
|
|
||||||
JBSQ = "jbsq"
|
|
||||||
MALODY = "malody"
|
|
||||||
MEMON_LEGACY = "memon:legacy"
|
|
||||||
MEMON_0_1_0 = "memon:v0.1.0"
|
|
||||||
MEMON_0_2_0 = "memon:v0.2.0"
|
|
||||||
MEMON_0_3_0 = "memon:v0.3.0"
|
|
||||||
MEMON_1_0_0 = "memon:v1.0.0"
|
|
||||||
MONO_COLUMN = "mono-column"
|
|
||||||
MEMO = "memo"
|
|
||||||
MEMO_1 = "memo1"
|
|
||||||
MEMO_2 = "memo2"
|
|
@ -2,7 +2,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .enum import Format
|
from .format_names import Format
|
||||||
|
|
||||||
|
|
||||||
def guess_format(path: Path) -> Format:
|
def guess_format(path: Path) -> Format:
|
||||||
|
@ -349,4 +349,4 @@ def _load_memo_file(lines: List[str]) -> Song:
|
|||||||
def load_memo(path: Path, **kwargs: Any) -> Song:
|
def load_memo(path: Path, **kwargs: Any) -> Song:
|
||||||
files = load_folder(path)
|
files = load_folder(path)
|
||||||
charts = [_load_memo_file(lines) for _, lines in files.items()]
|
charts = [_load_memo_file(lines) for _, lines in files.items()]
|
||||||
return Song.from_monochart_instances(charts)
|
return Song.from_monochart_instances(*charts)
|
||||||
|
@ -340,4 +340,4 @@ def _load_memo1_file(lines: List[str]) -> Song:
|
|||||||
def load_memo1(path: Path, **kwargs: Any) -> Song:
|
def load_memo1(path: Path, **kwargs: Any) -> Song:
|
||||||
files = load_folder(path)
|
files = load_folder(path)
|
||||||
charts = [_load_memo1_file(lines) for _, lines in files.items()]
|
charts = [_load_memo1_file(lines) for _, lines in files.items()]
|
||||||
return Song.from_monochart_instances(charts)
|
return Song.from_monochart_instances(*charts)
|
||||||
|
@ -459,4 +459,4 @@ def _load_memo2_file(lines: List[str]) -> Song:
|
|||||||
def load_memo2(path: Path, **kwargs: Any) -> Song:
|
def load_memo2(path: Path, **kwargs: Any) -> Song:
|
||||||
files = load_folder(path)
|
files = load_folder(path)
|
||||||
charts = [_load_memo2_file(lines) for _, lines in files.items()]
|
charts = [_load_memo2_file(lines) for _, lines in files.items()]
|
||||||
return Song.from_monochart_instances(charts)
|
return Song.from_monochart_instances(*charts)
|
||||||
|
@ -246,7 +246,7 @@ class MonoColumnParser(JubeatAnalyserParser):
|
|||||||
def load_mono_column(path: Path, **kwargs: Any) -> Song:
|
def load_mono_column(path: Path, **kwargs: Any) -> Song:
|
||||||
files = load_folder(path)
|
files = load_folder(path)
|
||||||
charts = [_load_mono_column_file(lines) for _, lines in files.items()]
|
charts = [_load_mono_column_file(lines) for _, lines in files.items()]
|
||||||
return Song.from_monochart_instances(charts)
|
return Song.from_monochart_instances(*charts)
|
||||||
|
|
||||||
|
|
||||||
def _load_mono_column_file(lines: List[str]) -> Song:
|
def _load_mono_column_file(lines: List[str]) -> Song:
|
||||||
|
@ -7,7 +7,7 @@ from hypothesis import note as hypothesis_note
|
|||||||
from hypothesis import strategies as st
|
from hypothesis import strategies as st
|
||||||
|
|
||||||
from jubeatools import song
|
from jubeatools import song
|
||||||
from jubeatools.formats.enum import Format
|
from jubeatools.formats.format_names import Format
|
||||||
from jubeatools.formats.jubeat_analyser.memo.dump import _dump_memo_chart
|
from jubeatools.formats.jubeat_analyser.memo.dump import _dump_memo_chart
|
||||||
from jubeatools.formats.jubeat_analyser.memo.load import MemoParser
|
from jubeatools.formats.jubeat_analyser.memo.load import MemoParser
|
||||||
from jubeatools.testutils import strategies as jbst
|
from jubeatools.testutils import strategies as jbst
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import math
|
import math
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from functools import singledispatch
|
from functools import singledispatch
|
||||||
from typing import List
|
from typing import List, Optional, Set
|
||||||
|
|
||||||
from more_itertools import numeric_range
|
from more_itertools import numeric_range
|
||||||
|
|
||||||
@ -11,10 +11,12 @@ from jubeatools.formats.timemap import TimeMap
|
|||||||
from .commons import AnyNote, Command, Event, bpm_to_value, ticks_at_beat
|
from .commons import AnyNote, Command, Event, bpm_to_value, ticks_at_beat
|
||||||
|
|
||||||
|
|
||||||
def make_events_from_chart(notes: List[AnyNote], timing: song.Timing) -> List[Event]:
|
def make_events_from_chart(
|
||||||
|
notes: List[AnyNote], timing: song.Timing, hakus: Optional[Set[song.BeatsTime]]
|
||||||
|
) -> List[Event]:
|
||||||
time_map = TimeMap.from_timing(timing)
|
time_map = TimeMap.from_timing(timing)
|
||||||
note_events = make_note_events(notes, time_map)
|
note_events = make_note_events(notes, time_map)
|
||||||
timing_events = make_timing_events(notes, timing, time_map)
|
timing_events = make_timing_events(notes, timing, hakus, time_map)
|
||||||
return sorted(note_events + timing_events)
|
return sorted(note_events + timing_events)
|
||||||
|
|
||||||
|
|
||||||
@ -38,14 +40,21 @@ def make_long_note_event(note: song.LongNote, time_map: TimeMap) -> Event:
|
|||||||
|
|
||||||
|
|
||||||
def make_timing_events(
|
def make_timing_events(
|
||||||
notes: List[AnyNote], timing: song.Timing, time_map: TimeMap
|
notes: List[AnyNote],
|
||||||
|
timing: song.Timing,
|
||||||
|
hakus: Optional[Set[song.BeatsTime]],
|
||||||
|
time_map: TimeMap,
|
||||||
) -> List[Event]:
|
) -> List[Event]:
|
||||||
bpm_events = [make_bpm_event(e, time_map) for e in timing.events]
|
bpm_events = [make_bpm_event(e, time_map) for e in timing.events]
|
||||||
end_beat = choose_end_beat(notes)
|
end_beat = choose_end_beat(notes)
|
||||||
end_event = make_end_event(end_beat, time_map)
|
end_event = make_end_event(end_beat, time_map)
|
||||||
measure_events = make_measure_events(end_beat, time_map)
|
measure_events = make_measure_events(end_beat, time_map)
|
||||||
beat_events = make_beat_events(end_beat, time_map)
|
if hakus is not None:
|
||||||
return bpm_events + measure_events + beat_events + [end_event]
|
haku_events = dump_hakus(hakus, time_map)
|
||||||
|
else:
|
||||||
|
haku_events = make_regular_hakus(end_beat, time_map)
|
||||||
|
|
||||||
|
return bpm_events + measure_events + haku_events + [end_event]
|
||||||
|
|
||||||
|
|
||||||
def make_bpm_event(bpm_change: song.BPMEvent, time_map: TimeMap) -> Event:
|
def make_bpm_event(bpm_change: song.BPMEvent, time_map: TimeMap) -> Event:
|
||||||
@ -94,14 +103,18 @@ def make_measure_event(beat: song.BeatsTime, time_map: TimeMap) -> Event:
|
|||||||
return Event(time=ticks, command=Command.MEASURE, value=0)
|
return Event(time=ticks, command=Command.MEASURE, value=0)
|
||||||
|
|
||||||
|
|
||||||
def make_beat_events(end_beat: song.BeatsTime, time_map: TimeMap) -> List[Event]:
|
def dump_hakus(hakus: Set[song.BeatsTime], time_map: TimeMap) -> List[Event]:
|
||||||
|
return [make_haku_event(beat, time_map) for beat in sorted(hakus)]
|
||||||
|
|
||||||
|
|
||||||
|
def make_regular_hakus(end_beat: song.BeatsTime, time_map: TimeMap) -> List[Event]:
|
||||||
start = song.BeatsTime(0)
|
start = song.BeatsTime(0)
|
||||||
stop = end_beat + song.BeatsTime(1, 2)
|
stop = end_beat + song.BeatsTime(1, 2)
|
||||||
step = song.BeatsTime(1)
|
step = song.BeatsTime(1)
|
||||||
beats = numeric_range(start, stop, step)
|
beats = numeric_range(start, stop, step)
|
||||||
return [make_beat_event(beat, time_map) for beat in beats]
|
return [make_haku_event(beat, time_map) for beat in beats]
|
||||||
|
|
||||||
|
|
||||||
def make_beat_event(beat: song.BeatsTime, time_map: TimeMap) -> Event:
|
def make_haku_event(beat: song.BeatsTime, time_map: TimeMap) -> Event:
|
||||||
ticks = ticks_at_beat(beat, time_map)
|
ticks = ticks_at_beat(beat, time_map)
|
||||||
return Event(time=ticks, command=Command.HAKU, value=0)
|
return Event(time=ticks, command=Command.HAKU, value=0)
|
||||||
|
@ -10,8 +10,8 @@ from ..dump_tools import make_events_from_chart
|
|||||||
|
|
||||||
def _dump_eve(song: song.Song, **kwargs: dict) -> List[ChartFile]:
|
def _dump_eve(song: song.Song, **kwargs: dict) -> List[ChartFile]:
|
||||||
res = []
|
res = []
|
||||||
for dif, chart, timing in song.iter_charts_with_applicable_timing():
|
for dif, chart, timing, hakus in song.iter_charts():
|
||||||
events = make_events_from_chart(chart.notes, timing)
|
events = make_events_from_chart(chart.notes, timing, hakus)
|
||||||
chart_text = "\n".join(e.dump() for e in events)
|
chart_text = "\n".join(e.dump() for e in events)
|
||||||
chart_bytes = chart_text.encode("ascii")
|
chart_bytes = chart_text.encode("ascii")
|
||||||
res.append(ChartFile(chart_bytes, song, dif, chart))
|
res.append(ChartFile(chart_bytes, song, dif, chart))
|
||||||
|
@ -11,7 +11,7 @@ from ..load_tools import make_chart_from_events
|
|||||||
def load_eve(path: Path, *, beat_snap: int = 240, **kwargs: Any) -> song.Song:
|
def load_eve(path: Path, *, beat_snap: int = 240, **kwargs: Any) -> song.Song:
|
||||||
files = load_folder(path)
|
files = load_folder(path)
|
||||||
charts = [_load_eve(l, p, beat_snap=beat_snap) for p, l in files.items()]
|
charts = [_load_eve(l, p, beat_snap=beat_snap) for p, l in files.items()]
|
||||||
return song.Song.from_monochart_instances(charts)
|
return song.Song.from_monochart_instances(*charts)
|
||||||
|
|
||||||
|
|
||||||
def load_file(path: Path) -> List[str]:
|
def load_file(path: Path) -> List[str]:
|
||||||
|
@ -15,8 +15,8 @@ from . import construct
|
|||||||
|
|
||||||
def _dump_jbsq(song: song.Song, **kwargs: dict) -> List[ChartFile]:
|
def _dump_jbsq(song: song.Song, **kwargs: dict) -> List[ChartFile]:
|
||||||
res = []
|
res = []
|
||||||
for dif, chart, timing in song.iter_charts_with_applicable_timing():
|
for dif, chart, timing, hakus in song.iter_charts():
|
||||||
events = make_events_from_chart(chart.notes, timing)
|
events = make_events_from_chart(chart.notes, timing, hakus)
|
||||||
jbsq_chart = make_jbsq_chart(events, chart.notes)
|
jbsq_chart = make_jbsq_chart(events, chart.notes)
|
||||||
chart_bytes = construct.jbsq.build(jbsq_chart)
|
chart_bytes = construct.jbsq.build(jbsq_chart)
|
||||||
res.append(ChartFile(chart_bytes, song, dif, chart))
|
res.append(ChartFile(chart_bytes, song, dif, chart))
|
||||||
|
@ -15,7 +15,7 @@ def load_jbsq(path: Path, *, beat_snap: int = 240, **kwargs: Any) -> song.Song:
|
|||||||
load_jbsq_file(bytes_, path, beat_snap=beat_snap)
|
load_jbsq_file(bytes_, path, beat_snap=beat_snap)
|
||||||
for path, bytes_ in files.items()
|
for path, bytes_ in files.items()
|
||||||
]
|
]
|
||||||
return song.Song.from_monochart_instances(charts)
|
return song.Song.from_monochart_instances(*charts)
|
||||||
|
|
||||||
|
|
||||||
def load_file(path: Path) -> bytes:
|
def load_file(path: Path) -> bytes:
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Iterable, List
|
from typing import Iterable, List, Optional, Set
|
||||||
|
|
||||||
|
from more_itertools import numeric_range
|
||||||
|
|
||||||
from jubeatools import song
|
from jubeatools import song
|
||||||
from jubeatools.formats.load_tools import round_beats
|
from jubeatools.formats.load_tools import round_beats
|
||||||
@ -36,17 +38,22 @@ def make_chart_from_events(events: Iterable[Event], beat_snap: int = 240) -> son
|
|||||||
]
|
]
|
||||||
all_notes = sorted(tap_notes + long_notes, key=lambda n: (n.time, n.position))
|
all_notes = sorted(tap_notes + long_notes, key=lambda n: (n.time, n.position))
|
||||||
timing = time_map.convert_to_timing_info(beat_snap=beat_snap)
|
timing = time_map.convert_to_timing_info(beat_snap=beat_snap)
|
||||||
return song.Chart(level=Decimal(0), timing=timing, notes=all_notes)
|
end_tick = events_by_command[Command.END].pop().time
|
||||||
|
hakus = make_hakus(
|
||||||
|
[e.time for e in events_by_command[Command.HAKU]],
|
||||||
|
end_tick,
|
||||||
|
time_map,
|
||||||
|
beat_snap,
|
||||||
|
)
|
||||||
|
return song.Chart(level=Decimal(0), timing=timing, notes=all_notes, hakus=hakus)
|
||||||
|
|
||||||
|
|
||||||
def make_tap_note(
|
def make_tap_note(
|
||||||
ticks: int, value: int, time_map: TimeMap, beat_snap: int
|
ticks: int, value: int, time_map: TimeMap, beat_snap: int
|
||||||
) -> song.TapNote:
|
) -> song.TapNote:
|
||||||
seconds = ticks_to_seconds(ticks)
|
time = beats_at_tick(ticks, time_map, beat_snap)
|
||||||
raw_beats = time_map.beats_at(seconds)
|
|
||||||
beats = round_beats(raw_beats, beat_snap)
|
|
||||||
position = song.NotePosition.from_index(value)
|
position = song.NotePosition.from_index(value)
|
||||||
return song.TapNote(time=beats, position=position)
|
return song.TapNote(time=time, position=position)
|
||||||
|
|
||||||
|
|
||||||
def make_long_note(
|
def make_long_note(
|
||||||
@ -67,3 +74,52 @@ def make_long_note(
|
|||||||
return song.LongNote(
|
return song.LongNote(
|
||||||
time=beats, position=position, duration=beats_duration, tail_tip=tail_pos
|
time=beats, position=position, duration=beats_duration, tail_tip=tail_pos
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_hakus(
|
||||||
|
hakus: List[int], end: int, time_map: TimeMap, beat_snap: int
|
||||||
|
) -> Optional[Set[song.BeatsTime]]:
|
||||||
|
"""Try to detect if the haku pattern is regular, in which case return None,
|
||||||
|
otherwise return the parsed hakus"""
|
||||||
|
roughly_rounded_hakus = make_raw_hakus(hakus, time_map, beat_snap=4)
|
||||||
|
rough_end = beats_at_tick(end, time_map, beat_snap=4)
|
||||||
|
if follows_regular_haku_pattern(roughly_rounded_hakus, rough_end):
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return make_raw_hakus(hakus, time_map, beat_snap)
|
||||||
|
|
||||||
|
|
||||||
|
def make_raw_hakus(
|
||||||
|
hakus: List[int], time_map: TimeMap, beat_snap: int
|
||||||
|
) -> Set[song.BeatsTime]:
|
||||||
|
return set(beats_at_tick(haku, time_map, beat_snap) for haku in hakus)
|
||||||
|
|
||||||
|
|
||||||
|
def follows_regular_haku_pattern(
|
||||||
|
hakus: Set[song.BeatsTime], end_command: song.BeatsTime
|
||||||
|
) -> bool:
|
||||||
|
"""Regular hakus extend at least till the END command in a 4/4 rhythm"""
|
||||||
|
if len(hakus) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
start = min(hakus)
|
||||||
|
if (start % 1) != 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
haku_end = max(hakus)
|
||||||
|
if (haku_end % 1) != 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if haku_end < end_command:
|
||||||
|
return False
|
||||||
|
|
||||||
|
stop = haku_end + song.BeatsTime(1, 2)
|
||||||
|
step = song.BeatsTime(1)
|
||||||
|
regular = numeric_range(start, stop, step)
|
||||||
|
return sorted(hakus) == list(regular)
|
||||||
|
|
||||||
|
|
||||||
|
def beats_at_tick(tick: int, time_map: TimeMap, beat_snap: int) -> song.BeatsTime:
|
||||||
|
seconds = ticks_to_seconds(tick)
|
||||||
|
raw_beats = time_map.beats_at(seconds)
|
||||||
|
return round_beats(raw_beats, beat_snap)
|
||||||
|
@ -12,7 +12,7 @@ simple_beat_strat = jbst.beat_time(
|
|||||||
|
|
||||||
@st.composite
|
@st.composite
|
||||||
def eve_compatible_song(draw: st.DrawFn) -> song.Song:
|
def eve_compatible_song(draw: st.DrawFn) -> song.Song:
|
||||||
"""eve only keeps notes, timing info and difficulty,
|
"""eve only keeps notes, hakus, timing info and difficulty,
|
||||||
the precision you can get out of it is also severly limited"""
|
the precision you can get out of it is also severly limited"""
|
||||||
diff = draw(st.sampled_from(list(song.Difficulty)))
|
diff = draw(st.sampled_from(list(song.Difficulty)))
|
||||||
chart = draw(
|
chart = draw(
|
||||||
@ -42,6 +42,7 @@ def eve_compatible_song(draw: st.DrawFn) -> song.Song:
|
|||||||
beat_time_strat=simple_beat_strat,
|
beat_time_strat=simple_beat_strat,
|
||||||
),
|
),
|
||||||
level_strat=st.just(Decimal(0)),
|
level_strat=st.just(Decimal(0)),
|
||||||
|
hakus_strat=st.one_of(st.none(), st.sets(simple_beat_strat)),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return song.Song(
|
return song.Song(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from . import jubeat_analyser, konami, malody, memon
|
from . import jubeat_analyser, konami, malody, memon
|
||||||
from .enum import Format
|
from .format_names import Format
|
||||||
from .typing import Dumper, Loader
|
from .typing import Dumper, Loader
|
||||||
|
|
||||||
LOADERS: Dict[Format, Loader] = {
|
LOADERS: Dict[Format, Loader] = {
|
||||||
|
@ -18,7 +18,7 @@ from . import schema as malody
|
|||||||
def load_malody(path: Path, **kwargs: Any) -> song.Song:
|
def load_malody(path: Path, **kwargs: Any) -> song.Song:
|
||||||
files = load_folder(path)
|
files = load_folder(path)
|
||||||
charts = [load_malody_file(d) for d in files.values()]
|
charts = [load_malody_file(d) for d in files.values()]
|
||||||
return song.Song.from_monochart_instances(charts)
|
return song.Song.from_monochart_instances(*charts)
|
||||||
|
|
||||||
|
|
||||||
def load_file(path: Path) -> Any:
|
def load_file(path: Path) -> Any:
|
||||||
|
@ -31,7 +31,7 @@ def make_memon_folder_loader(memon_loader: Callable[[Any], jbt.Song]) -> Loader:
|
|||||||
)
|
)
|
||||||
|
|
||||||
charts = [memon_loader(d) for d in files.values()]
|
charts = [memon_loader(d) for d in files.values()]
|
||||||
return jbt.Song.from_monochart_instances(charts)
|
return jbt.Song.from_monochart_instances(*charts)
|
||||||
|
|
||||||
return load
|
return load
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Union
|
from typing import Any, Dict, Union
|
||||||
|
|
||||||
from multidict import MultiDict
|
|
||||||
|
|
||||||
from jubeatools import song as jbt
|
from jubeatools import song as jbt
|
||||||
from jubeatools.utils import none_or
|
from jubeatools.utils import none_or
|
||||||
@ -38,18 +36,17 @@ def _load_memon_legacy(raw_memon: Any) -> jbt.Song:
|
|||||||
events=[jbt.BPMEvent(time=jbt.BeatsTime(0), BPM=file["metadata"]["BPM"])],
|
events=[jbt.BPMEvent(time=jbt.BeatsTime(0), BPM=file["metadata"]["BPM"])],
|
||||||
beat_zero_offset=jbt.SecondsTime(-file["metadata"]["offset"]),
|
beat_zero_offset=jbt.SecondsTime(-file["metadata"]["offset"]),
|
||||||
)
|
)
|
||||||
charts: MultiDict[jbt.Chart] = MultiDict()
|
charts: Dict[str, jbt.Chart] = {}
|
||||||
for memon_chart in file["data"]:
|
for memon_chart in file["data"]:
|
||||||
charts.add(
|
difficulty = memon_chart["dif_name"]
|
||||||
memon_chart["dif_name"],
|
chart = jbt.Chart(
|
||||||
jbt.Chart(
|
level=memon_chart["level"],
|
||||||
level=memon_chart["level"],
|
notes=[
|
||||||
notes=[
|
_load_memon_note_v0(note, memon_chart["resolution"])
|
||||||
_load_memon_note_v0(note, memon_chart["resolution"])
|
for note in memon_chart["notes"]
|
||||||
for note in memon_chart["notes"]
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
charts[difficulty] = chart
|
||||||
|
|
||||||
return jbt.Song(metadata=metadata, charts=charts, common_timing=common_timing)
|
return jbt.Song(metadata=metadata, charts=charts, common_timing=common_timing)
|
||||||
|
|
||||||
@ -70,18 +67,16 @@ def _load_memon_0_1_0(raw_memon: Any) -> jbt.Song:
|
|||||||
events=[jbt.BPMEvent(time=jbt.BeatsTime(0), BPM=file["metadata"]["BPM"])],
|
events=[jbt.BPMEvent(time=jbt.BeatsTime(0), BPM=file["metadata"]["BPM"])],
|
||||||
beat_zero_offset=jbt.SecondsTime(-file["metadata"]["offset"]),
|
beat_zero_offset=jbt.SecondsTime(-file["metadata"]["offset"]),
|
||||||
)
|
)
|
||||||
charts: MultiDict[jbt.Chart] = MultiDict()
|
charts: Dict[str, jbt.Chart] = {}
|
||||||
for difficulty, memon_chart in file["data"].items():
|
for difficulty, memon_chart in file["data"].items():
|
||||||
charts.add(
|
chart = jbt.Chart(
|
||||||
difficulty,
|
level=memon_chart["level"],
|
||||||
jbt.Chart(
|
notes=[
|
||||||
level=memon_chart["level"],
|
_load_memon_note_v0(note, memon_chart["resolution"])
|
||||||
notes=[
|
for note in memon_chart["notes"]
|
||||||
_load_memon_note_v0(note, memon_chart["resolution"])
|
],
|
||||||
for note in memon_chart["notes"]
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
charts[difficulty] = chart
|
||||||
|
|
||||||
return jbt.Song(metadata=metadata, charts=charts, common_timing=common_timing)
|
return jbt.Song(metadata=metadata, charts=charts, common_timing=common_timing)
|
||||||
|
|
||||||
@ -109,18 +104,16 @@ def _load_memon_0_2_0(raw_memon: Any) -> jbt.Song:
|
|||||||
events=[jbt.BPMEvent(time=jbt.BeatsTime(0), BPM=file["metadata"]["BPM"])],
|
events=[jbt.BPMEvent(time=jbt.BeatsTime(0), BPM=file["metadata"]["BPM"])],
|
||||||
beat_zero_offset=jbt.SecondsTime(-file["metadata"]["offset"]),
|
beat_zero_offset=jbt.SecondsTime(-file["metadata"]["offset"]),
|
||||||
)
|
)
|
||||||
charts: MultiDict[jbt.Chart] = MultiDict()
|
charts: Dict[str, jbt.Chart] = {}
|
||||||
for difficulty, memon_chart in file["data"].items():
|
for difficulty, memon_chart in file["data"].items():
|
||||||
charts.add(
|
chart = jbt.Chart(
|
||||||
difficulty,
|
level=memon_chart["level"],
|
||||||
jbt.Chart(
|
notes=[
|
||||||
level=memon_chart["level"],
|
_load_memon_note_v0(note, memon_chart["resolution"])
|
||||||
notes=[
|
for note in memon_chart["notes"]
|
||||||
_load_memon_note_v0(note, memon_chart["resolution"])
|
],
|
||||||
for note in memon_chart["notes"]
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
charts[difficulty] = chart
|
||||||
|
|
||||||
return jbt.Song(metadata=metadata, charts=charts, common_timing=common_timing)
|
return jbt.Song(metadata=metadata, charts=charts, common_timing=common_timing)
|
||||||
|
|
||||||
@ -149,18 +142,16 @@ def _load_memon_0_3_0(raw_memon: Any) -> jbt.Song:
|
|||||||
events=[jbt.BPMEvent(time=jbt.BeatsTime(0), BPM=file["metadata"]["BPM"])],
|
events=[jbt.BPMEvent(time=jbt.BeatsTime(0), BPM=file["metadata"]["BPM"])],
|
||||||
beat_zero_offset=jbt.SecondsTime(-file["metadata"]["offset"]),
|
beat_zero_offset=jbt.SecondsTime(-file["metadata"]["offset"]),
|
||||||
)
|
)
|
||||||
charts: MultiDict[jbt.Chart] = MultiDict()
|
charts: Dict[str, jbt.Chart] = {}
|
||||||
for difficulty, memon_chart in file["data"].items():
|
for difficulty, memon_chart in file["data"].items():
|
||||||
charts.add(
|
chart = jbt.Chart(
|
||||||
difficulty,
|
level=memon_chart["level"],
|
||||||
jbt.Chart(
|
notes=[
|
||||||
level=memon_chart["level"],
|
_load_memon_note_v0(note, memon_chart["resolution"])
|
||||||
notes=[
|
for note in memon_chart["notes"]
|
||||||
_load_memon_note_v0(note, memon_chart["resolution"])
|
],
|
||||||
for note in memon_chart["notes"]
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
charts[difficulty] = chart
|
||||||
|
|
||||||
return jbt.Song(metadata=metadata, charts=charts, common_timing=common_timing)
|
return jbt.Song(metadata=metadata, charts=charts, common_timing=common_timing)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import hypothesis.strategies as st
|
|||||||
from hypothesis import given
|
from hypothesis import given
|
||||||
|
|
||||||
from jubeatools import song
|
from jubeatools import song
|
||||||
from jubeatools.formats.enum import Format
|
from jubeatools.formats.format_names import Format
|
||||||
from jubeatools.testutils import strategies as jbst
|
from jubeatools.testutils import strategies as jbst
|
||||||
from jubeatools.testutils.test_patterns import dump_and_load_then_compare
|
from jubeatools.testutils.test_patterns import dump_and_load_then_compare
|
||||||
|
|
||||||
|
@ -12,9 +12,7 @@ from ..tools import make_memon_dumper
|
|||||||
from . import schema as memon
|
from . import schema as memon
|
||||||
|
|
||||||
|
|
||||||
def _dump_memon_1_0_0(
|
def _dump_memon_1_0_0(song: jbt.Song, **kwargs: Any) -> SongFile:
|
||||||
song: jbt.Song, use_fractions: bool = False, **kwargs: Any
|
|
||||||
) -> SongFile:
|
|
||||||
metadata = dump_metadata(song.metadata)
|
metadata = dump_metadata(song.metadata)
|
||||||
common_timing = dump_file_timing(song)
|
common_timing = dump_file_timing(song)
|
||||||
charts = {
|
charts = {
|
||||||
|
@ -5,7 +5,7 @@ import hypothesis.strategies as st
|
|||||||
from hypothesis import given
|
from hypothesis import given
|
||||||
|
|
||||||
from jubeatools import song
|
from jubeatools import song
|
||||||
from jubeatools.formats.enum import Format
|
from jubeatools.formats.format_names import Format
|
||||||
from jubeatools.testutils import strategies as jbst
|
from jubeatools.testutils import strategies as jbst
|
||||||
from jubeatools.testutils.test_patterns import dump_and_load_then_compare
|
from jubeatools.testutils.test_patterns import dump_and_load_then_compare
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ from pathlib import Path
|
|||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
|
Dict,
|
||||||
Iterable,
|
Iterable,
|
||||||
Iterator,
|
Iterator,
|
||||||
List,
|
List,
|
||||||
@ -28,8 +29,6 @@ from typing import (
|
|||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
from multidict import MultiDict
|
|
||||||
|
|
||||||
from jubeatools.utils import none_or
|
from jubeatools.utils import none_or
|
||||||
|
|
||||||
BeatsTime = Fraction
|
BeatsTime = Fraction
|
||||||
@ -223,14 +222,13 @@ class Metadata:
|
|||||||
preview_file: Optional[Path] = None
|
preview_file: Optional[Path] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def permissive_merge(cls, metadatas: Iterable["Metadata"]) -> "Metadata":
|
def permissive_merge(cls, *metadatas: "Metadata") -> "Metadata":
|
||||||
"""Make the "sum" of all the given metadata instances, if possible. If
|
"""Make the "sum" of all the given metadata instances, if possible. If
|
||||||
several instances have different defined values for the same field,
|
several instances have different defined values for the same field,
|
||||||
merging will fail. Fields with Noneor empty values (empty string or
|
merging will fail. Fields with Noneor empty values (empty string or
|
||||||
empty path) are conscidered undefined and their values can be replaced
|
empty path) are conscidered undefined and their values can be replaced
|
||||||
by an actual value if supplied by at least one object from the given
|
by an actual value if supplied by at least one object from the given
|
||||||
iterable."""
|
iterable."""
|
||||||
metadatas = list(metadatas)
|
|
||||||
return cls(
|
return cls(
|
||||||
**{f.name: _get_common_value(f, metadatas) for f in fields(cls)},
|
**{f.name: _get_common_value(f, metadatas) for f in fields(cls)},
|
||||||
)
|
)
|
||||||
@ -280,18 +278,18 @@ class Song:
|
|||||||
A Song is a set of charts with associated metadata"""
|
A Song is a set of charts with associated metadata"""
|
||||||
|
|
||||||
metadata: Metadata
|
metadata: Metadata
|
||||||
charts: Mapping[str, Chart] = field(default_factory=MultiDict)
|
charts: Mapping[str, Chart] = field(default_factory=dict)
|
||||||
common_timing: Optional[Timing] = None
|
common_timing: Optional[Timing] = None
|
||||||
common_hakus: Optional[Set[BeatsTime]] = None
|
common_hakus: Optional[Set[BeatsTime]] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_monochart_instances(cls, songs: Iterable["Song"]) -> "Song":
|
def from_monochart_instances(cls, *songs: "Song") -> "Song":
|
||||||
metadata = Metadata.permissive_merge(song.metadata for song in songs)
|
metadata = Metadata.permissive_merge(*(song.metadata for song in songs))
|
||||||
charts: MultiDict[Chart] = MultiDict()
|
charts: Dict[str, Chart] = {}
|
||||||
for song in songs:
|
for song in songs:
|
||||||
song.remove_common_timing()
|
song.remove_common_timing()
|
||||||
song.remove_common_hakus()
|
song.remove_common_hakus()
|
||||||
charts.extend(song.charts)
|
charts.update(song.charts)
|
||||||
|
|
||||||
merged = cls(
|
merged = cls(
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
@ -351,3 +349,15 @@ class Song:
|
|||||||
f"Neither song nor {dif} chart have any timing information"
|
f"Neither song nor {dif} chart have any timing information"
|
||||||
)
|
)
|
||||||
yield dif, chart, timing
|
yield dif, chart, timing
|
||||||
|
|
||||||
|
def iter_charts(
|
||||||
|
self,
|
||||||
|
) -> Iterator[Tuple[str, Chart, Timing, Optional[Set[BeatsTime]]]]:
|
||||||
|
for dif, chart in self.charts.items():
|
||||||
|
timing = chart.timing or self.common_timing
|
||||||
|
if timing is None:
|
||||||
|
raise ValueError(
|
||||||
|
f"Neither song nor {dif} chart have any timing information"
|
||||||
|
)
|
||||||
|
hakus = chart.hakus if chart.hakus is not None else self.common_hakus
|
||||||
|
yield dif, chart, timing, hakus
|
||||||
|
@ -10,7 +10,6 @@ from pathlib import Path
|
|||||||
from typing import Dict, Iterable, Optional, Set, Union
|
from typing import Dict, Iterable, Optional, Set, Union
|
||||||
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
from multidict import MultiDict
|
|
||||||
|
|
||||||
from jubeatools.song import (
|
from jubeatools.song import (
|
||||||
BeatsTime,
|
BeatsTime,
|
||||||
@ -286,14 +285,9 @@ def song(
|
|||||||
chart_strat: st.SearchStrategy[Chart] = chart(),
|
chart_strat: st.SearchStrategy[Chart] = chart(),
|
||||||
metadata_strat: st.SearchStrategy[Metadata] = metadata(),
|
metadata_strat: st.SearchStrategy[Metadata] = metadata(),
|
||||||
) -> Song:
|
) -> Song:
|
||||||
diffs = draw(diffs_strat)
|
|
||||||
charts: MultiDict[Chart] = MultiDict()
|
|
||||||
for diff_name in diffs:
|
|
||||||
charts.add(diff_name, draw(chart_strat))
|
|
||||||
|
|
||||||
return Song(
|
return Song(
|
||||||
metadata=draw(metadata_strat),
|
metadata=draw(metadata_strat),
|
||||||
charts=charts,
|
charts={difficulty: draw(chart_strat) for difficulty in draw(diffs_strat)},
|
||||||
common_timing=draw(common_timing_strat),
|
common_timing=draw(common_timing_strat),
|
||||||
common_hakus=draw(common_hakus_strat),
|
common_hakus=draw(common_hakus_strat),
|
||||||
)
|
)
|
||||||
|
@ -6,8 +6,7 @@ from typing import Callable, ContextManager, Iterator, Optional
|
|||||||
from hypothesis import note
|
from hypothesis import note
|
||||||
|
|
||||||
from jubeatools import song
|
from jubeatools import song
|
||||||
from jubeatools.formats import DUMPERS, LOADERS
|
from jubeatools.formats import DUMPERS, LOADERS, Format
|
||||||
from jubeatools.formats.enum import Format
|
|
||||||
from jubeatools.formats.guess import guess_format
|
from jubeatools.formats.guess import guess_format
|
||||||
|
|
||||||
|
|
||||||
|
84
poetry.lock
generated
84
poetry.lock
generated
@ -238,14 +238,6 @@ category = "main"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "multidict"
|
|
||||||
version = "5.2.0"
|
|
||||||
description = "multidict implementation"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "0.910"
|
version = "0.910"
|
||||||
@ -523,7 +515,7 @@ typing-extensions = ">=3.7.4"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.8"
|
python-versions = "^3.8"
|
||||||
content-hash = "41155bca4070edc9eb6e6369890af55397251caeab4e5c2db62ef7785410853e"
|
content-hash = "971f32ab1478240f072615b119a1df87aebccfb26e6ee4cf013d938ff15db690"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
atomicwrites = [
|
atomicwrites = [
|
||||||
@ -596,80 +588,6 @@ more-itertools = [
|
|||||||
{file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"},
|
{file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"},
|
||||||
{file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"},
|
{file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"},
|
||||||
]
|
]
|
||||||
multidict = [
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b61f85101ef08cbbc37846ac0e43f027f7844f3fade9b7f6dd087178caedeee7"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9b668c065968c5979fe6b6fa6760bb6ab9aeb94b75b73c0a9c1acf6393ac3bf"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517d75522b7b18a3385726b54a081afd425d4f41144a5399e5abd97ccafdf36b"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b4ac3ba7a97b35a5ccf34f41b5a8642a01d1e55454b699e5e8e7a99b5a3acf5"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:df23c83398715b26ab09574217ca21e14694917a0c857e356fd39e1c64f8283f"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e58a9b5cc96e014ddf93c2227cbdeca94b56a7eb77300205d6e4001805391747"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f76440e480c3b2ca7f843ff8a48dc82446b86ed4930552d736c0bac507498a52"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfde464ca4af42a629648c0b0d79b8f295cf5b695412451716531d6916461628"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0fed465af2e0eb6357ba95795d003ac0bdb546305cc2366b1fc8f0ad67cc3fda"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b70913cbf2e14275013be98a06ef4b412329fe7b4f83d64eb70dce8269ed1e1a"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5635bcf1b75f0f6ef3c8a1ad07b500104a971e38d3683167b9454cb6465ac86"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-win32.whl", hash = "sha256:77f0fb7200cc7dedda7a60912f2059086e29ff67cefbc58d2506638c1a9132d7"},
|
|
||||||
{file = "multidict-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:9416cf11bcd73c861267e88aea71e9fcc35302b3943e45e1dbb4317f91a4b34f"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd77c8f3cba815aa69cb97ee2b2ef385c7c12ada9c734b0f3b32e26bb88bbf1d"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ec9aea6223adf46999f22e2c0ab6cf33f5914be604a404f658386a8f1fba37"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5283c0a00f48e8cafcecadebfa0ed1dac8b39e295c7248c44c665c16dc1138b"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f79c19c6420962eb17c7e48878a03053b7ccd7b69f389d5831c0a4a7f1ac0a1"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4a67f1080123de76e4e97a18d10350df6a7182e243312426d508712e99988d4"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94b117e27efd8e08b4046c57461d5a114d26b40824995a2eb58372b94f9fca02"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2e77282fd1d677c313ffcaddfec236bf23f273c4fba7cdf198108f5940ae10f5"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:116347c63ba049c1ea56e157fa8aa6edaf5e92925c9b64f3da7769bdfa012858"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:dc3a866cf6c13d59a01878cd806f219340f3e82eed514485e094321f24900677"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac42181292099d91217a82e3fa3ce0e0ddf3a74fd891b7c2b347a7f5aa0edded"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f0bb0973f42ffcb5e3537548e0767079420aefd94ba990b61cf7bb8d47f4916d"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:ea21d4d5104b4f840b91d9dc8cbc832aba9612121eaba503e54eaab1ad140eb9"},
|
|
||||||
{file = "multidict-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6453f3cbeb78440747096f239d282cc57a2997a16b5197c9bc839099e1633d0"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3def943bfd5f1c47d51fd324df1e806d8da1f8e105cc7f1c76a1daf0f7e17b0"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35591729668a303a02b06e8dba0eb8140c4a1bfd4c4b3209a436a02a5ac1de11"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8cacda0b679ebc25624d5de66c705bc53dcc7c6f02a7fb0f3ca5e227d80422"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:baf1856fab8212bf35230c019cde7c641887e3fc08cadd39d32a421a30151ea3"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43616aec0f0d53c411582c451f5d3e1123a68cc7b3475d6f7d97a626f8ff90d"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25cbd39a9029b409167aa0a20d8a17f502d43f2efebfe9e3ac019fe6796c59ac"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a2cbcfbea6dc776782a444db819c8b78afe4db597211298dd8b2222f73e9cd0"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d2d7d1fff8e09d99354c04c3fd5b560fb04639fd45926b34e27cfdec678a704"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a37e9a68349f6abe24130846e2f1d2e38f7ddab30b81b754e5a1fde32f782b23"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:637c1896497ff19e1ee27c1c2c2ddaa9f2d134bbb5e0c52254361ea20486418d"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9815765f9dcda04921ba467957be543423e5ec6a1136135d84f2ae092c50d87b"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:8b911d74acdc1fe2941e59b4f1a278a330e9c34c6c8ca1ee21264c51ec9b67ef"},
|
|
||||||
{file = "multidict-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:380b868f55f63d048a25931a1632818f90e4be71d2081c2338fcf656d299949a"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7d81ce5744757d2f05fc41896e3b2ae0458464b14b5a2c1e87a6a9d69aefaa8"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1d55cdf706ddc62822d394d1df53573d32a7a07d4f099470d3cb9323b721b6"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4771d0d0ac9d9fe9e24e33bed482a13dfc1256d008d101485fe460359476065"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7d57ea65744d249427793c042094c4016789eb2562576fb831870f9c878d9e"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd68778f96216596218b4e8882944d24a634d984ee1a5a049b300377878fa7c"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecc99bce8ee42dcad15848c7885197d26841cb24fa2ee6e89d23b8993c871c64"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:067150fad08e6f2dd91a650c7a49ba65085303fcc3decbd64a57dc13a2733031"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78c106b2b506b4d895ddc801ff509f941119394b89c9115580014127414e6c2d"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6c4fa1ec16e01e292315ba76eb1d012c025b99d22896bd14a66628b245e3e01"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b227345e4186809d31f22087d0265655114af7cda442ecaf72246275865bebe4"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:06560fbdcf22c9387100979e65b26fba0816c162b888cb65b845d3def7a54c9b"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7878b61c867fb2df7a95e44b316f88d5a3742390c99dfba6c557a21b30180cac"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:246145bff76cc4b19310f0ad28bd0769b940c2a49fc601b86bfd150cbd72bb22"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-win32.whl", hash = "sha256:c30ac9f562106cd9e8071c23949a067b10211917fdcb75b4718cf5775356a940"},
|
|
||||||
{file = "multidict-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:f19001e790013ed580abfde2a4465388950728861b52f0da73e8e8a9418533c0"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c1ff762e2ee126e6f1258650ac641e2b8e1f3d927a925aafcfde943b77a36d24"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd6c9c50bf2ad3f0448edaa1a3b55b2e6866ef8feca5d8dbec10ec7c94371d21"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc66d4016f6e50ed36fb39cd287a3878ffcebfa90008535c62e0e90a7ab713ae"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9acb76d5f3dd9421874923da2ed1e76041cb51b9337fd7f507edde1d86535d6"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfc924a7e946dd3c6360e50e8f750d51e3ef5395c95dc054bc9eab0f70df4f9c"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fdba7333eb2351fee2596b756d730d62b5827d5e1ab2f84e6cbb287cc67fe0"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9aad49466b8d828b96b9e3630006234879c8d3e2b0a9d99219b3121bc5cdb17"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:93de39267c4c676c9ebb2057e98a8138bade0d806aad4d864322eee0803140a0"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9bef5cff994ca3026fcc90680e326d1a19df9841c5e3d224076407cc21471a1"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5f841c4f14331fd1e36cbf3336ed7be2cb2a8f110ce40ea253e5573387db7621"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:38ba256ee9b310da6a1a0f013ef4e422fca30a685bcbec86a969bd520504e341"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3bc3b1621b979621cee9f7b09f024ec76ec03cc365e638126a056317470bde1b"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ee908c070020d682e9b42c8f621e8bb10c767d04416e2ebe44e37d0f44d9ad5"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-win32.whl", hash = "sha256:1c7976cd1c157fa7ba5456ae5d31ccdf1479680dc9b8d8aa28afabc370df42b8"},
|
|
||||||
{file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"},
|
|
||||||
{file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"},
|
|
||||||
]
|
|
||||||
mypy = [
|
mypy = [
|
||||||
{file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
|
{file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
|
||||||
{file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
|
{file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
|
||||||
|
@ -8,7 +8,6 @@ repository = "https://github.com/Stepland/jubeatools"
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = "^3.8"
|
||||||
multidict = "^5.1.0"
|
|
||||||
click = "^8.0.3"
|
click = "^8.0.3"
|
||||||
path = "^15.1.2"
|
path = "^15.1.2"
|
||||||
simplejson = "^3.17.0"
|
simplejson = "^3.17.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user