1
0
mirror of synced 2024-12-11 22:46:00 +01:00

Merge pull request #21 from Stepland/more-stuff

More stuff
This commit is contained in:
Stepland 2021-12-28 16:57:06 +01:00 committed by GitHub
commit 587e68ab3e
34 changed files with 313 additions and 207 deletions

View File

@ -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

View File

@ -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,

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View 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"""

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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]:

View File

@ -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))

View File

@ -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:

View File

@ -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)

View File

@ -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(

View File

@ -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] = {

View File

@ -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:

View File

@ -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

View File

@ -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,
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)
@ -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,
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)
@ -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,
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)

View File

@ -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

View File

@ -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 = {

View File

@ -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

View File

@ -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

View File

@ -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),
) )

View File

@ -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
View File

@ -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"},

View File

@ -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"