pyrekordbox 0.4.2__py3-none-any.whl → 0.4.4__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/__main__.py +1 -51
- pyrekordbox/_version.py +16 -3
- pyrekordbox/anlz/__init__.py +6 -6
- pyrekordbox/anlz/file.py +56 -43
- pyrekordbox/anlz/tags.py +108 -70
- pyrekordbox/config.py +18 -356
- pyrekordbox/db6/aux_files.py +40 -14
- pyrekordbox/db6/database.py +384 -236
- pyrekordbox/db6/registry.py +48 -34
- pyrekordbox/db6/smartlist.py +12 -12
- pyrekordbox/db6/tables.py +60 -58
- pyrekordbox/mysettings/__init__.py +3 -2
- pyrekordbox/mysettings/file.py +27 -24
- pyrekordbox/rbxml.py +321 -142
- pyrekordbox/utils.py +27 -6
- {pyrekordbox-0.4.2.dist-info → pyrekordbox-0.4.4.dist-info}/METADATA +13 -38
- pyrekordbox-0.4.4.dist-info/RECORD +25 -0
- {pyrekordbox-0.4.2.dist-info → pyrekordbox-0.4.4.dist-info}/WHEEL +1 -1
- {pyrekordbox-0.4.2.dist-info → pyrekordbox-0.4.4.dist-info}/licenses/LICENSE +1 -1
- pyrekordbox-0.4.2.dist-info/RECORD +0 -25
- {pyrekordbox-0.4.2.dist-info → pyrekordbox-0.4.4.dist-info}/top_level.txt +0 -0
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:
|