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/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
- data = structs.AnlzTag.build(self.struct)
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
- return self.content.entry_count
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
- data = structs.AnlzTag.build(self.struct)
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
- return self.content.path
355
+ def path(self) -> str:
356
+ path: str = self.content.path
357
+ return path
322
358
 
323
- def get(self):
324
- return self.content.path
359
+ def get(self) -> str:
360
+ return self.path
325
361
 
326
- def set(self, path):
327
- path = path.replace("\\", "/")
328
- len_path = len(path.encode("utf-16-be")) + 2
329
- self.content.path = 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: