pyrekordbox 0.4.2__py3-none-any.whl → 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyrekordbox/__init__.py +1 -0
- pyrekordbox/_version.py +2 -2
- pyrekordbox/anlz/__init__.py +6 -6
- pyrekordbox/anlz/file.py +40 -32
- pyrekordbox/anlz/tags.py +108 -70
- pyrekordbox/config.py +30 -24
- pyrekordbox/db6/aux_files.py +40 -14
- pyrekordbox/db6/database.py +379 -217
- pyrekordbox/db6/registry.py +48 -34
- pyrekordbox/db6/smartlist.py +12 -12
- pyrekordbox/db6/tables.py +51 -52
- pyrekordbox/mysettings/__init__.py +3 -2
- pyrekordbox/mysettings/file.py +27 -24
- pyrekordbox/rbxml.py +321 -142
- pyrekordbox/utils.py +7 -6
- {pyrekordbox-0.4.2.dist-info → pyrekordbox-0.4.3.dist-info}/METADATA +1 -1
- pyrekordbox-0.4.3.dist-info/RECORD +25 -0
- {pyrekordbox-0.4.2.dist-info → pyrekordbox-0.4.3.dist-info}/WHEEL +1 -1
- pyrekordbox-0.4.2.dist-info/RECORD +0 -25
- {pyrekordbox-0.4.2.dist-info → pyrekordbox-0.4.3.dist-info}/licenses/LICENSE +0 -0
- {pyrekordbox-0.4.2.dist-info → pyrekordbox-0.4.3.dist-info}/top_level.txt +0 -0
pyrekordbox/__init__.py
CHANGED
pyrekordbox/_version.py
CHANGED
pyrekordbox/anlz/__init__.py
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
import re
|
6
6
|
from pathlib import Path
|
7
|
-
from typing import Union
|
7
|
+
from typing import Dict, Iterator, Tuple, Union
|
8
8
|
|
9
9
|
from . import structs
|
10
10
|
from .file import AnlzFile
|
@@ -42,7 +42,7 @@ def is_anlz_file(path: Union[str, Path]) -> bool:
|
|
42
42
|
return bool(RE_ANLZ.match(path.name))
|
43
43
|
|
44
44
|
|
45
|
-
def get_anlz_paths(root: Union[str, Path]) ->
|
45
|
+
def get_anlz_paths(root: Union[str, Path]) -> Dict[str, Union[Path, None]]:
|
46
46
|
"""Returns the paths of all existing ANLZ files in a directory.
|
47
47
|
|
48
48
|
Parameters
|
@@ -62,14 +62,14 @@ def get_anlz_paths(root: Union[str, Path]) -> dict:
|
|
62
62
|
>>> p["DAT"]
|
63
63
|
directory/ANLZ0000.DAT
|
64
64
|
"""
|
65
|
-
paths = {"DAT": None, "EXT": None, "2EX": None}
|
65
|
+
paths: Dict[str, Union[Path, None]] = {"DAT": None, "EXT": None, "2EX": None}
|
66
66
|
for path in Path(root).iterdir():
|
67
67
|
if RE_ANLZ.match(path.name):
|
68
68
|
paths[path.suffix[1:].upper()] = path
|
69
69
|
return paths
|
70
70
|
|
71
71
|
|
72
|
-
def walk_anlz_dirs(root_dir: Union[str, Path]):
|
72
|
+
def walk_anlz_dirs(root_dir: Union[str, Path]) -> Iterator[Path]:
|
73
73
|
"""Finds all ANLZ directory paths recursively.
|
74
74
|
|
75
75
|
Parameters
|
@@ -88,7 +88,7 @@ def walk_anlz_dirs(root_dir: Union[str, Path]):
|
|
88
88
|
yield path
|
89
89
|
|
90
90
|
|
91
|
-
def walk_anlz_paths(root_dir: Union[str, Path]):
|
91
|
+
def walk_anlz_paths(root_dir: Union[str, Path]) -> Iterator[Tuple[Path, Dict[str, Path]]]:
|
92
92
|
"""Finds all ANLZ directory paths and the containing file paths recursively.
|
93
93
|
|
94
94
|
Parameters
|
@@ -110,7 +110,7 @@ def walk_anlz_paths(root_dir: Union[str, Path]):
|
|
110
110
|
yield anlz_dir, files
|
111
111
|
|
112
112
|
|
113
|
-
def read_anlz_files(root: Union[str, Path] = "") ->
|
113
|
+
def read_anlz_files(root: Union[str, Path] = "") -> Dict[Path, AnlzFile]:
|
114
114
|
"""Open all ANLZ files in the given root directory.
|
115
115
|
|
116
116
|
Parameters
|
pyrekordbox/anlz/file.py
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
import logging
|
6
6
|
from collections import abc
|
7
7
|
from pathlib import Path
|
8
|
-
from typing import Union
|
8
|
+
from typing import Any, Iterator, List, Union
|
9
9
|
|
10
|
-
from construct import Int16ub
|
10
|
+
from construct import Int16ub, Struct
|
11
11
|
|
12
12
|
from . import structs
|
13
|
-
from .tags import TAGS
|
13
|
+
from .tags import TAGS, AbstractAnlzTag, StructNotInitializedError
|
14
14
|
|
15
15
|
logger = logging.getLogger(__name__)
|
16
16
|
|
@@ -18,35 +18,35 @@ XOR_MASK = bytearray.fromhex("CB E1 EE FA E5 EE AD EE E9 D2 E9 EB E1 E9 F3 E8 E9
|
|
18
18
|
|
19
19
|
|
20
20
|
class BuildFileLengthError(Exception):
|
21
|
-
def __init__(self, struct, len_data):
|
21
|
+
def __init__(self, struct: Struct, len_data: int) -> None:
|
22
22
|
super().__init__(
|
23
23
|
f"`len_file` ({struct.len_file}) of '{struct.type}' does not "
|
24
24
|
f"match the data-length ({len_data})!"
|
25
25
|
)
|
26
26
|
|
27
27
|
|
28
|
-
class AnlzFile(abc.Mapping):
|
28
|
+
class AnlzFile(abc.Mapping): # type: ignore[type-arg]
|
29
29
|
"""Rekordbox `ANLZnnnn.xxx` binary file handler."""
|
30
30
|
|
31
|
-
def __init__(self):
|
32
|
-
self._path = ""
|
33
|
-
self.file_header = None
|
34
|
-
self.tags = list()
|
31
|
+
def __init__(self) -> None:
|
32
|
+
self._path: str = ""
|
33
|
+
self.file_header: Union[Struct, None] = None
|
34
|
+
self.tags: List[AbstractAnlzTag] = list()
|
35
35
|
|
36
36
|
@property
|
37
|
-
def num_tags(self):
|
37
|
+
def num_tags(self) -> int:
|
38
38
|
return len(self.tags)
|
39
39
|
|
40
40
|
@property
|
41
|
-
def tag_types(self):
|
41
|
+
def tag_types(self) -> List[str]:
|
42
42
|
return [tag.type for tag in self.tags]
|
43
43
|
|
44
44
|
@property
|
45
|
-
def path(self):
|
45
|
+
def path(self) -> str:
|
46
46
|
return self._path
|
47
47
|
|
48
48
|
@classmethod
|
49
|
-
def parse(cls, data: bytes):
|
49
|
+
def parse(cls, data: bytes) -> "AnlzFile":
|
50
50
|
"""Parses the in-memory data of a Rekordbox analysis binary file.
|
51
51
|
|
52
52
|
Parameters
|
@@ -64,7 +64,7 @@ class AnlzFile(abc.Mapping):
|
|
64
64
|
return self
|
65
65
|
|
66
66
|
@classmethod
|
67
|
-
def parse_file(cls, path: Union[str, Path]):
|
67
|
+
def parse_file(cls, path: Union[str, Path]) -> "AnlzFile":
|
68
68
|
"""Reads and parses a Rekordbox analysis binary file.
|
69
69
|
|
70
70
|
Parameters
|
@@ -92,10 +92,10 @@ class AnlzFile(abc.Mapping):
|
|
92
92
|
data = fh.read()
|
93
93
|
|
94
94
|
self = cls.parse(data)
|
95
|
-
self._path = path
|
95
|
+
self._path = str(path)
|
96
96
|
return self
|
97
97
|
|
98
|
-
def _parse(self, data: bytes):
|
98
|
+
def _parse(self, data: bytes) -> None:
|
99
99
|
file_header = structs.AnlzFileHeader.parse(data)
|
100
100
|
tag_type = file_header.type
|
101
101
|
assert tag_type == "PMAI"
|
@@ -136,6 +136,8 @@ class AnlzFile(abc.Mapping):
|
|
136
136
|
try:
|
137
137
|
# Parse the struct
|
138
138
|
tag = TAGS[tag_type](tag_data)
|
139
|
+
if tag.struct is None:
|
140
|
+
raise StructNotInitializedError()
|
139
141
|
tags.append(tag)
|
140
142
|
len_header = tag.struct.len_header
|
141
143
|
len_tag = tag.struct.len_tag
|
@@ -147,28 +149,34 @@ class AnlzFile(abc.Mapping):
|
|
147
149
|
)
|
148
150
|
except KeyError:
|
149
151
|
logger.warning("Tag '%s' not supported!", tag_type)
|
150
|
-
|
151
|
-
len_tag =
|
152
|
+
tag_struct = structs.AnlzTag.parse(tag_data)
|
153
|
+
len_tag = tag_struct.len_tag
|
152
154
|
i += len_tag
|
153
155
|
|
154
156
|
self.file_header = file_header
|
155
157
|
self.tags = tags
|
156
158
|
|
157
|
-
def update_len(self):
|
159
|
+
def update_len(self) -> None:
|
158
160
|
# Update struct lengths
|
161
|
+
if self.file_header is None:
|
162
|
+
raise StructNotInitializedError()
|
159
163
|
tags_len = 0
|
160
164
|
for tag in self.tags:
|
165
|
+
if tag.struct is None:
|
166
|
+
raise StructNotInitializedError()
|
161
167
|
tag.update_len()
|
162
168
|
tags_len += tag.struct.len_tag
|
163
169
|
# Update file length
|
164
170
|
len_file = self.file_header.len_header + tags_len
|
165
171
|
self.file_header.len_file = len_file
|
166
172
|
|
167
|
-
def build(self):
|
173
|
+
def build(self) -> bytes:
|
174
|
+
if self.file_header is None:
|
175
|
+
raise StructNotInitializedError()
|
168
176
|
self.update_len()
|
169
177
|
header_data = structs.AnlzFileHeader.build(self.file_header)
|
170
178
|
section_data = b"".join(tag.build() for tag in self.tags)
|
171
|
-
data = header_data + section_data
|
179
|
+
data: bytes = header_data + section_data
|
172
180
|
# Check `len_file`
|
173
181
|
len_file = self.file_header.len_file
|
174
182
|
len_data = len(data)
|
@@ -177,38 +185,38 @@ class AnlzFile(abc.Mapping):
|
|
177
185
|
|
178
186
|
return data
|
179
187
|
|
180
|
-
def save(self, path=""):
|
188
|
+
def save(self, path: Union[str, Path] = "") -> None:
|
181
189
|
path = path or self._path
|
182
190
|
|
183
191
|
data = self.build()
|
184
192
|
with open(path, "wb") as fh:
|
185
193
|
fh.write(data)
|
186
194
|
|
187
|
-
def get_tag(self, key):
|
195
|
+
def get_tag(self, key: str) -> AbstractAnlzTag:
|
188
196
|
return self.__getitem__(key)[0]
|
189
197
|
|
190
|
-
def getall_tags(self, key):
|
198
|
+
def getall_tags(self, key: str) -> List[AbstractAnlzTag]:
|
191
199
|
return self.__getitem__(key)
|
192
200
|
|
193
|
-
def get(self, key):
|
201
|
+
def get(self, key: str) -> Any: # type: ignore[override]
|
194
202
|
return self.__getitem__(key)[0].get()
|
195
203
|
|
196
|
-
def getall(self, key):
|
204
|
+
def getall(self, key: str) -> List[Any]:
|
197
205
|
return [tag.get() for tag in self.__getitem__(key)]
|
198
206
|
|
199
|
-
def __len__(self):
|
207
|
+
def __len__(self) -> int:
|
200
208
|
return len(self.keys())
|
201
209
|
|
202
|
-
def __iter__(self):
|
210
|
+
def __iter__(self) -> Iterator[str]:
|
203
211
|
return iter(set(tag.type for tag in self.tags))
|
204
212
|
|
205
|
-
def __getitem__(self, item):
|
213
|
+
def __getitem__(self, item: str) -> List[AbstractAnlzTag]:
|
206
214
|
if item.isupper() and len(item) == 4:
|
207
215
|
return [tag for tag in self.tags if tag.type == item]
|
208
216
|
else:
|
209
217
|
return [tag for tag in self.tags if tag.name == item]
|
210
218
|
|
211
|
-
def __contains__(self, item):
|
219
|
+
def __contains__(self, item: str) -> bool: # type: ignore[override]
|
212
220
|
if item.isupper() and len(item) == 4:
|
213
221
|
for tag in self.tags:
|
214
222
|
if item == tag.type:
|
@@ -219,9 +227,9 @@ class AnlzFile(abc.Mapping):
|
|
219
227
|
return True
|
220
228
|
return False
|
221
229
|
|
222
|
-
def __repr__(self):
|
230
|
+
def __repr__(self) -> str:
|
223
231
|
return f"{self.__class__.__name__}({self.tag_types})"
|
224
232
|
|
225
|
-
def set_path(self, path):
|
233
|
+
def set_path(self, path: Union[Path, str]) -> None:
|
226
234
|
tag = self.get_tag("PPTH")
|
227
235
|
tag.set(path)
|
pyrekordbox/anlz/tags.py
CHANGED
@@ -4,16 +4,28 @@
|
|
4
4
|
|
5
5
|
import logging
|
6
6
|
from abc import ABC
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Any, Sequence, Tuple, Union
|
7
9
|
|
8
10
|
import numpy as np
|
11
|
+
import numpy.typing as npt
|
12
|
+
from construct import Struct
|
13
|
+
from construct.lib.containers import Container
|
9
14
|
|
10
15
|
from . import structs
|
11
16
|
|
12
17
|
logger = logging.getLogger(__name__)
|
13
18
|
|
14
19
|
|
20
|
+
class StructNotInitializedError(Exception):
|
21
|
+
"""Raised when a struct is not initialized."""
|
22
|
+
|
23
|
+
def __init__(self) -> None:
|
24
|
+
super().__init__("Struct is not initialized!")
|
25
|
+
|
26
|
+
|
15
27
|
class BuildTagLengthError(Exception):
|
16
|
-
def __init__(self, struct, len_data):
|
28
|
+
def __init__(self, struct: Struct, len_data: int) -> None:
|
17
29
|
super().__init__(
|
18
30
|
f"`len_tag` ({struct.len_tag}) of '{struct.type}' does not "
|
19
31
|
f"match the data-length ({len_data})!"
|
@@ -28,16 +40,20 @@ class AbstractAnlzTag(ABC):
|
|
28
40
|
LEN_HEADER: int = 0 # Expected value of `len_header`
|
29
41
|
LEN_TAG: int = 0 # Expected value of `len_tag`
|
30
42
|
|
31
|
-
def __init__(self, tag_data):
|
32
|
-
self.struct = None
|
43
|
+
def __init__(self, tag_data: bytes) -> None:
|
44
|
+
self.struct: Union[Struct, None] = None
|
33
45
|
if tag_data is not None:
|
34
46
|
self.parse(tag_data)
|
35
47
|
|
36
48
|
@property
|
37
|
-
def content(self):
|
49
|
+
def content(self) -> Container:
|
50
|
+
if self.struct is None:
|
51
|
+
raise StructNotInitializedError()
|
38
52
|
return self.struct.content
|
39
53
|
|
40
|
-
def _check_len_header(self):
|
54
|
+
def _check_len_header(self) -> None:
|
55
|
+
if self.struct is None:
|
56
|
+
raise StructNotInitializedError()
|
41
57
|
if self.LEN_HEADER != self.struct.len_header:
|
42
58
|
logger.warning(
|
43
59
|
"`len_header` (%s) of `%s` doesn't match the expected value %s",
|
@@ -46,7 +62,9 @@ class AbstractAnlzTag(ABC):
|
|
46
62
|
self.LEN_HEADER,
|
47
63
|
)
|
48
64
|
|
49
|
-
def _check_len_tag(self):
|
65
|
+
def _check_len_tag(self) -> None:
|
66
|
+
if self.struct is None:
|
67
|
+
raise StructNotInitializedError()
|
50
68
|
if self.LEN_TAG != self.struct.len_tag:
|
51
69
|
logger.warning(
|
52
70
|
"`len_tag` (%s) of `%s` doesn't match the expected value %s",
|
@@ -55,10 +73,10 @@ class AbstractAnlzTag(ABC):
|
|
55
73
|
self.LEN_TAG,
|
56
74
|
)
|
57
75
|
|
58
|
-
def check_parse(self):
|
76
|
+
def check_parse(self) -> None:
|
59
77
|
pass
|
60
78
|
|
61
|
-
def parse(self, tag_data):
|
79
|
+
def parse(self, tag_data: bytes) -> None:
|
62
80
|
self.struct = structs.AnlzTag.parse(tag_data)
|
63
81
|
if self.LEN_HEADER:
|
64
82
|
self._check_len_header()
|
@@ -66,32 +84,40 @@ class AbstractAnlzTag(ABC):
|
|
66
84
|
self._check_len_tag()
|
67
85
|
self.check_parse()
|
68
86
|
|
69
|
-
def build(self):
|
70
|
-
|
87
|
+
def build(self) -> bytes:
|
88
|
+
if self.struct is None:
|
89
|
+
raise StructNotInitializedError()
|
90
|
+
data: bytes = structs.AnlzTag.build(self.struct)
|
71
91
|
len_data = len(data)
|
72
92
|
if len_data != self.struct.len_tag:
|
73
93
|
raise BuildTagLengthError(self.struct, len_data)
|
74
94
|
return data
|
75
95
|
|
76
|
-
def get(self):
|
96
|
+
def get(self) -> Container:
|
97
|
+
if self.struct is None:
|
98
|
+
raise StructNotInitializedError()
|
77
99
|
return self.struct.content
|
78
100
|
|
79
|
-
def set(self, *args, **kwargs):
|
101
|
+
def set(self, *args: Any, **kwargs: Any) -> None:
|
80
102
|
pass
|
81
103
|
|
82
|
-
def update_len(self):
|
104
|
+
def update_len(self) -> None:
|
83
105
|
pass
|
84
106
|
|
85
|
-
def __repr__(self):
|
107
|
+
def __repr__(self) -> str:
|
108
|
+
if self.struct is None:
|
109
|
+
raise StructNotInitializedError()
|
86
110
|
len_header = self.struct.len_header
|
87
111
|
len_tag = self.struct.len_tag
|
88
112
|
return f"{self.__class__.__name__}(len_header={len_header}, len_tag={len_tag})"
|
89
113
|
|
90
|
-
def pformat(self):
|
114
|
+
def pformat(self) -> str:
|
115
|
+
if self.struct is None:
|
116
|
+
raise StructNotInitializedError()
|
91
117
|
return str(self.struct)
|
92
118
|
|
93
119
|
|
94
|
-
def _parse_wf_preview(tag):
|
120
|
+
def _parse_wf_preview(tag: structs.AnlzTag) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.int8]]:
|
95
121
|
n = len(tag.entries)
|
96
122
|
wf = np.zeros(n, dtype=np.int8)
|
97
123
|
col = np.zeros(n, dtype=np.int8)
|
@@ -110,32 +136,32 @@ class PQTZAnlzTag(AbstractAnlzTag):
|
|
110
136
|
LEN_HEADER = 24
|
111
137
|
|
112
138
|
@property
|
113
|
-
def count(self):
|
139
|
+
def count(self) -> int:
|
114
140
|
return len(self.content.entries)
|
115
141
|
|
116
142
|
@property
|
117
|
-
def beats(self):
|
143
|
+
def beats(self) -> npt.NDArray[np.int8]:
|
118
144
|
return self.get_beats()
|
119
145
|
|
120
146
|
@property
|
121
|
-
def bpms(self):
|
147
|
+
def bpms(self) -> npt.NDArray[np.float64]:
|
122
148
|
return self.get_bpms()
|
123
149
|
|
124
150
|
@property
|
125
|
-
def bpms_average(self):
|
151
|
+
def bpms_average(self) -> float:
|
126
152
|
if len(self.content.entries):
|
127
|
-
return np.mean(self.get_bpms())
|
153
|
+
return float(np.mean(self.get_bpms()))
|
128
154
|
return 0.0
|
129
155
|
|
130
156
|
@property
|
131
|
-
def bpms_unique(self):
|
157
|
+
def bpms_unique(self) -> Any:
|
132
158
|
return np.unique(self.get_bpms())
|
133
159
|
|
134
160
|
@property
|
135
|
-
def times(self):
|
161
|
+
def times(self) -> npt.NDArray[np.float64]:
|
136
162
|
return self.get_times()
|
137
163
|
|
138
|
-
def get(self):
|
164
|
+
def get(self) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.float64], npt.NDArray[np.float64]]:
|
139
165
|
n = len(self.content.entries)
|
140
166
|
beats = np.zeros(n, dtype=np.int8)
|
141
167
|
bpms = np.zeros(n, dtype=np.float64)
|
@@ -147,16 +173,16 @@ class PQTZAnlzTag(AbstractAnlzTag):
|
|
147
173
|
times[i] = time / 1_000 # Convert milliseconds to seconds
|
148
174
|
return beats, bpms, times
|
149
175
|
|
150
|
-
def get_beats(self):
|
176
|
+
def get_beats(self) -> npt.NDArray[np.int8]:
|
151
177
|
return np.array([entry.beat for entry in self.content.entries], dtype=np.int8)
|
152
178
|
|
153
|
-
def get_bpms(self):
|
154
|
-
return np.array([entry.tempo / 100 for entry in self.content.entries])
|
179
|
+
def get_bpms(self) -> npt.NDArray[np.float64]:
|
180
|
+
return np.array([entry.tempo / 100 for entry in self.content.entries], dtype=np.float64)
|
155
181
|
|
156
|
-
def get_times(self):
|
157
|
-
return np.array([entry.time / 1000 for entry in self.content.entries])
|
182
|
+
def get_times(self) -> npt.NDArray[np.float64]:
|
183
|
+
return np.array([entry.time / 1000 for entry in self.content.entries], dtype=np.float64)
|
158
184
|
|
159
|
-
def set(self, beats, bpms, times):
|
185
|
+
def set(self, beats: Sequence[int], bpms: Sequence[float], times: Sequence[float]) -> None:
|
160
186
|
n = len(self.content.entries)
|
161
187
|
n_beats = len(beats)
|
162
188
|
n_bpms = len(bpms)
|
@@ -176,7 +202,7 @@ class PQTZAnlzTag(AbstractAnlzTag):
|
|
176
202
|
data = {"beat": int(beat), "tempo": int(100 * bpm), "time": int(1000 * t)}
|
177
203
|
self.content.entries[i].update(data)
|
178
204
|
|
179
|
-
def set_beats(self, beats):
|
205
|
+
def set_beats(self, beats: Sequence[int]) -> None:
|
180
206
|
n = len(self.content.entries)
|
181
207
|
n_new = len(beats)
|
182
208
|
if n_new != n:
|
@@ -185,7 +211,7 @@ class PQTZAnlzTag(AbstractAnlzTag):
|
|
185
211
|
for i, beat in enumerate(beats):
|
186
212
|
self.content.entries[i].beat = beat
|
187
213
|
|
188
|
-
def set_bpms(self, bpms):
|
214
|
+
def set_bpms(self, bpms: Sequence[float]) -> None:
|
189
215
|
n = len(self.content.entries)
|
190
216
|
n_new = len(bpms)
|
191
217
|
if n_new != n:
|
@@ -194,7 +220,7 @@ class PQTZAnlzTag(AbstractAnlzTag):
|
|
194
220
|
for i, bpm in enumerate(bpms):
|
195
221
|
self.content.entries[i].tempo = int(bpm * 100)
|
196
222
|
|
197
|
-
def set_times(self, times):
|
223
|
+
def set_times(self, times: Sequence[float]) -> None:
|
198
224
|
n = len(self.content.entries)
|
199
225
|
n_new = len(times)
|
200
226
|
if n_new != n:
|
@@ -203,10 +229,14 @@ class PQTZAnlzTag(AbstractAnlzTag):
|
|
203
229
|
for i, t in enumerate(times):
|
204
230
|
self.content.entries[i].time = int(1000 * t)
|
205
231
|
|
206
|
-
def check_parse(self):
|
232
|
+
def check_parse(self) -> None:
|
233
|
+
if self.struct is None:
|
234
|
+
raise StructNotInitializedError()
|
207
235
|
assert self.struct.content.entry_count == len(self.struct.content.entries)
|
208
236
|
|
209
|
-
def update_len(self):
|
237
|
+
def update_len(self) -> None:
|
238
|
+
if self.struct is None:
|
239
|
+
raise StructNotInitializedError()
|
210
240
|
self.struct.len_tag = self.struct.len_header + 8 * len(self.content.entries)
|
211
241
|
|
212
242
|
|
@@ -220,33 +250,36 @@ class PQT2AnlzTag(AbstractAnlzTag):
|
|
220
250
|
count = 2
|
221
251
|
|
222
252
|
@property
|
223
|
-
def beats(self):
|
253
|
+
def beats(self) -> npt.NDArray[np.int8]:
|
224
254
|
return self.get_beats()
|
225
255
|
|
226
256
|
@property
|
227
|
-
def bpms(self):
|
257
|
+
def bpms(self) -> npt.NDArray[np.float64]:
|
228
258
|
return self.get_bpms()
|
229
259
|
|
230
260
|
@property
|
231
|
-
def times(self):
|
261
|
+
def times(self) -> npt.NDArray[np.float64]:
|
232
262
|
return self.get_times()
|
233
263
|
|
234
264
|
@property
|
235
|
-
def beat_grid_count(self):
|
236
|
-
|
265
|
+
def beat_grid_count(self) -> int:
|
266
|
+
count: int = self.content.entry_count
|
267
|
+
return count
|
237
268
|
|
238
269
|
@property
|
239
|
-
def bpms_unique(self):
|
270
|
+
def bpms_unique(self) -> Any:
|
240
271
|
return np.unique(self.get_bpms())
|
241
272
|
|
242
|
-
def check_parse(self):
|
273
|
+
def check_parse(self) -> None:
|
274
|
+
if self.struct is None:
|
275
|
+
raise StructNotInitializedError()
|
243
276
|
len_beats = self.struct.content.entry_count
|
244
277
|
if len_beats:
|
245
278
|
expected = self.struct.len_tag - self.struct.len_header
|
246
279
|
actual = 2 * len(self.content.entries) # each entry consist of 2 bytes
|
247
280
|
assert actual == expected, f"{actual} != {expected}"
|
248
281
|
|
249
|
-
def get(self):
|
282
|
+
def get(self) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.float64], npt.NDArray[np.float64]]:
|
250
283
|
n = len(self.content.bpm)
|
251
284
|
beats = np.zeros(n, dtype=np.int8)
|
252
285
|
bpms = np.zeros(n, dtype=np.float64)
|
@@ -258,32 +291,34 @@ class PQT2AnlzTag(AbstractAnlzTag):
|
|
258
291
|
times[i] = time / 1_000 # Convert milliseconds to seconds
|
259
292
|
return beats, bpms, times
|
260
293
|
|
261
|
-
def get_beats(self):
|
294
|
+
def get_beats(self) -> npt.NDArray[np.int8]:
|
262
295
|
return np.array([entry.beat for entry in self.content.bpm], dtype=np.int8)
|
263
296
|
|
264
|
-
def get_bpms(self):
|
265
|
-
return np.array([entry.tempo / 100 for entry in self.content.bpm])
|
297
|
+
def get_bpms(self) -> npt.NDArray[np.float64]:
|
298
|
+
return np.array([entry.tempo / 100 for entry in self.content.bpm], dtype=np.float64)
|
266
299
|
|
267
|
-
def get_times(self):
|
268
|
-
return np.array([entry.time / 1000 for entry in self.content.bpm])
|
300
|
+
def get_times(self) -> npt.NDArray[np.float64]:
|
301
|
+
return np.array([entry.time / 1000 for entry in self.content.bpm], dtype=np.float64)
|
269
302
|
|
270
|
-
def get_beat_grid(self):
|
303
|
+
def get_beat_grid(self) -> npt.NDArray[np.int8]:
|
271
304
|
return np.array([entry.beat for entry in self.content.entries], dtype=np.int8)
|
272
305
|
|
273
|
-
def set_beats(self, beats):
|
306
|
+
def set_beats(self, beats: Sequence[int]) -> None:
|
274
307
|
for i, beat in enumerate(beats):
|
275
308
|
self.content.bpm[i].beat = beat
|
276
309
|
|
277
|
-
def set_bpms(self, bpms):
|
310
|
+
def set_bpms(self, bpms: Sequence[float]) -> None:
|
278
311
|
for i, bpm in enumerate(bpms):
|
279
312
|
self.content.bpm[i].bpm = int(bpm * 100)
|
280
313
|
|
281
|
-
def set_times(self, times):
|
314
|
+
def set_times(self, times: Sequence[float]) -> None:
|
282
315
|
for i, t in enumerate(times):
|
283
316
|
self.content.bpm[i].time = int(1000 * t)
|
284
317
|
|
285
|
-
def build(self):
|
286
|
-
|
318
|
+
def build(self) -> bytes:
|
319
|
+
if self.struct is None:
|
320
|
+
raise StructNotInitializedError()
|
321
|
+
data: bytes = structs.AnlzTag.build(self.struct)
|
287
322
|
if self.struct.content.entry_count == 0:
|
288
323
|
data = data[: self.struct.len_tag]
|
289
324
|
|
@@ -317,19 +352,22 @@ class PPTHAnlzTag(AbstractAnlzTag):
|
|
317
352
|
LEN_HEADER = 16
|
318
353
|
|
319
354
|
@property
|
320
|
-
def path(self):
|
321
|
-
|
355
|
+
def path(self) -> str:
|
356
|
+
path: str = self.content.path
|
357
|
+
return path
|
322
358
|
|
323
|
-
def get(self):
|
324
|
-
return self.
|
359
|
+
def get(self) -> str:
|
360
|
+
return self.path
|
325
361
|
|
326
|
-
def set(self, path):
|
327
|
-
|
328
|
-
len_path = len(
|
329
|
-
self.content.path =
|
362
|
+
def set(self, path: Union[str, Path]) -> None:
|
363
|
+
pathstr = str(path).replace("\\", "/")
|
364
|
+
len_path = len(pathstr.encode("utf-16-be")) + 2
|
365
|
+
self.content.path = pathstr
|
330
366
|
self.content.len_path = len_path
|
331
367
|
|
332
|
-
def update_len(self):
|
368
|
+
def update_len(self) -> None:
|
369
|
+
if self.struct is None:
|
370
|
+
raise StructNotInitializedError()
|
333
371
|
self.struct.len_tag = self.struct.len_header + self.content.len_path
|
334
372
|
|
335
373
|
|
@@ -341,8 +379,8 @@ class PVBRAnlzTag(AbstractAnlzTag):
|
|
341
379
|
LEN_HEADER = 16
|
342
380
|
LEN_TAG = 1620
|
343
381
|
|
344
|
-
def get(self):
|
345
|
-
return np.array(self.content.idx)
|
382
|
+
def get(self) -> npt.NDArray[np.uint64]:
|
383
|
+
return np.array(self.content.idx, dtype=np.uint64)
|
346
384
|
|
347
385
|
|
348
386
|
class PSSIAnlzTag(AbstractAnlzTag):
|
@@ -360,7 +398,7 @@ class PWAVAnlzTag(AbstractAnlzTag):
|
|
360
398
|
name = "wf_preview"
|
361
399
|
LEN_HEADER = 20
|
362
400
|
|
363
|
-
def get(self):
|
401
|
+
def get(self) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.int8]]:
|
364
402
|
return _parse_wf_preview(self.content)
|
365
403
|
|
366
404
|
|
@@ -371,7 +409,7 @@ class PWV2AnlzTag(AbstractAnlzTag):
|
|
371
409
|
name = "wf_tiny_preview"
|
372
410
|
LEN_HEADER = 20
|
373
411
|
|
374
|
-
def get(self):
|
412
|
+
def get(self) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.int8]]:
|
375
413
|
return _parse_wf_preview(self.content)
|
376
414
|
|
377
415
|
|
@@ -382,7 +420,7 @@ class PWV3AnlzTag(AbstractAnlzTag):
|
|
382
420
|
name = "wf_detail"
|
383
421
|
LEN_HEADER = 24
|
384
422
|
|
385
|
-
def get(self):
|
423
|
+
def get(self) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.int8]]:
|
386
424
|
return _parse_wf_preview(self.content)
|
387
425
|
|
388
426
|
|
@@ -393,7 +431,7 @@ class PWV4AnlzTag(AbstractAnlzTag):
|
|
393
431
|
name = "wf_color"
|
394
432
|
LEN_HEADER = 24
|
395
433
|
|
396
|
-
def get(self):
|
434
|
+
def get(self) -> Tuple[npt.NDArray[np.int64], npt.NDArray[np.int64], npt.NDArray[np.int64]]:
|
397
435
|
num_entries = self.content.len_entries
|
398
436
|
data = self.content.entries
|
399
437
|
ws, hs = 1, 1
|
@@ -430,7 +468,7 @@ class PWV5AnlzTag(AbstractAnlzTag):
|
|
430
468
|
name = "wf_color_detail"
|
431
469
|
LEN_HEADER = 24
|
432
470
|
|
433
|
-
def get(self):
|
471
|
+
def get(self) -> Tuple[npt.NDArray[np.float64], npt.NDArray[np.int64]]:
|
434
472
|
"""Parse the Waveform Color Detail Tag (PWV5).
|
435
473
|
|
436
474
|
The format of the entries is:
|