pyrekordbox 0.2.1__py3-none-any.whl → 0.2.2__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.
Files changed (71) hide show
  1. docs/source/formats/anlz.md +178 -7
  2. docs/source/formats/db6.md +1 -1
  3. docs/source/index.md +2 -6
  4. docs/source/quickstart.md +68 -45
  5. docs/source/tutorial/index.md +1 -1
  6. pyrekordbox/__init__.py +1 -1
  7. pyrekordbox/_version.py +2 -2
  8. pyrekordbox/anlz/file.py +39 -0
  9. pyrekordbox/anlz/structs.py +3 -5
  10. pyrekordbox/config.py +71 -27
  11. pyrekordbox/db6/database.py +260 -33
  12. pyrekordbox/db6/registry.py +22 -0
  13. pyrekordbox/db6/tables.py +3 -4
  14. {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/METADATA +12 -11
  15. pyrekordbox-0.2.2.dist-info/RECORD +80 -0
  16. {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/top_level.txt +0 -2
  17. tests/test_config.py +175 -0
  18. tests/test_db6.py +78 -0
  19. build/lib/build/lib/docs/source/conf.py +0 -178
  20. build/lib/build/lib/pyrekordbox/__init__.py +0 -22
  21. build/lib/build/lib/pyrekordbox/__main__.py +0 -204
  22. build/lib/build/lib/pyrekordbox/_version.py +0 -16
  23. build/lib/build/lib/pyrekordbox/anlz/__init__.py +0 -127
  24. build/lib/build/lib/pyrekordbox/anlz/file.py +0 -186
  25. build/lib/build/lib/pyrekordbox/anlz/structs.py +0 -299
  26. build/lib/build/lib/pyrekordbox/anlz/tags.py +0 -508
  27. build/lib/build/lib/pyrekordbox/config.py +0 -596
  28. build/lib/build/lib/pyrekordbox/db6/__init__.py +0 -45
  29. build/lib/build/lib/pyrekordbox/db6/aux_files.py +0 -213
  30. build/lib/build/lib/pyrekordbox/db6/database.py +0 -1808
  31. build/lib/build/lib/pyrekordbox/db6/registry.py +0 -304
  32. build/lib/build/lib/pyrekordbox/db6/tables.py +0 -1618
  33. build/lib/build/lib/pyrekordbox/logger.py +0 -23
  34. build/lib/build/lib/pyrekordbox/mysettings/__init__.py +0 -32
  35. build/lib/build/lib/pyrekordbox/mysettings/file.py +0 -369
  36. build/lib/build/lib/pyrekordbox/mysettings/structs.py +0 -282
  37. build/lib/build/lib/pyrekordbox/utils.py +0 -162
  38. build/lib/build/lib/pyrekordbox/xml.py +0 -1294
  39. build/lib/build/lib/tests/__init__.py +0 -3
  40. build/lib/build/lib/tests/test_anlz.py +0 -206
  41. build/lib/build/lib/tests/test_db6.py +0 -1039
  42. build/lib/build/lib/tests/test_mysetting.py +0 -203
  43. build/lib/build/lib/tests/test_xml.py +0 -629
  44. build/lib/docs/source/conf.py +0 -178
  45. build/lib/pyrekordbox/__init__.py +0 -22
  46. build/lib/pyrekordbox/__main__.py +0 -204
  47. build/lib/pyrekordbox/_version.py +0 -16
  48. build/lib/pyrekordbox/anlz/__init__.py +0 -127
  49. build/lib/pyrekordbox/anlz/file.py +0 -186
  50. build/lib/pyrekordbox/anlz/structs.py +0 -299
  51. build/lib/pyrekordbox/anlz/tags.py +0 -508
  52. build/lib/pyrekordbox/config.py +0 -596
  53. build/lib/pyrekordbox/db6/__init__.py +0 -45
  54. build/lib/pyrekordbox/db6/aux_files.py +0 -213
  55. build/lib/pyrekordbox/db6/database.py +0 -1808
  56. build/lib/pyrekordbox/db6/registry.py +0 -304
  57. build/lib/pyrekordbox/db6/tables.py +0 -1618
  58. build/lib/pyrekordbox/logger.py +0 -23
  59. build/lib/pyrekordbox/mysettings/__init__.py +0 -32
  60. build/lib/pyrekordbox/mysettings/file.py +0 -369
  61. build/lib/pyrekordbox/mysettings/structs.py +0 -282
  62. build/lib/pyrekordbox/utils.py +0 -162
  63. build/lib/pyrekordbox/xml.py +0 -1294
  64. build/lib/tests/__init__.py +0 -3
  65. build/lib/tests/test_anlz.py +0 -206
  66. build/lib/tests/test_db6.py +0 -1039
  67. build/lib/tests/test_mysetting.py +0 -203
  68. build/lib/tests/test_xml.py +0 -629
  69. pyrekordbox-0.2.1.dist-info/RECORD +0 -129
  70. {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/LICENSE +0 -0
  71. {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/WHEEL +0 -0
@@ -1,508 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Author: Dylan Jones
3
- # Date: 2023-02-01
4
-
5
- import logging
6
- from abc import ABC
7
- import numpy as np
8
- from . import structs
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
-
13
- class BuildTagLengthError(Exception):
14
- def __init__(self, struct, len_data):
15
- super().__init__(
16
- f"`len_tag` ({struct.len_tag}) of '{struct.type}' does not "
17
- f"match the data-length ({len_data})!"
18
- )
19
-
20
-
21
- class AbstractAnlzTag(ABC):
22
- """Abstract base class for struct handlers of Rekordbox analysis files."""
23
-
24
- type: str
25
- name: str
26
- LEN_HEADER: int = 0 # Expected value of `len_header`
27
- LEN_TAG: int = 0 # Expected value of `len_tag`
28
-
29
- def __init__(self, tag_data):
30
- self.struct = None
31
- if tag_data is not None:
32
- self.parse(tag_data)
33
-
34
- @property
35
- def content(self):
36
- return self.struct.content
37
-
38
- def _check_len_header(self):
39
- if self.LEN_HEADER != self.struct.len_header:
40
- logger.warning(
41
- "`len_header` (%s) of `%s` doesn't match the expected value %s",
42
- self.struct.len_header,
43
- self.struct.type,
44
- self.LEN_HEADER,
45
- )
46
-
47
- def _check_len_tag(self):
48
- if self.LEN_TAG != self.struct.len_tag:
49
- logger.warning(
50
- "`len_tag` (%s) of `%s` doesn't match the expected value %s",
51
- self.struct.len_tag,
52
- self.struct.type,
53
- self.LEN_TAG,
54
- )
55
-
56
- def check_parse(self):
57
- pass
58
-
59
- def parse(self, tag_data):
60
- self.struct = structs.AnlzTag.parse(tag_data)
61
- if self.LEN_HEADER:
62
- self._check_len_header()
63
- if self.LEN_TAG:
64
- self._check_len_tag()
65
- self.check_parse()
66
-
67
- def build(self):
68
- data = structs.AnlzTag.build(self.struct)
69
- len_data = len(data)
70
- if len_data != self.struct.len_tag:
71
- raise BuildTagLengthError(self.struct, len_data)
72
- return data
73
-
74
- def get(self):
75
- return self.struct.content
76
-
77
- def set(self, *args, **kwargs):
78
- pass
79
-
80
- def update_len(self):
81
- pass
82
-
83
- def __repr__(self):
84
- len_header = self.struct.len_header
85
- len_tag = self.struct.len_tag
86
- return f"{self.__class__.__name__}(len_header={len_header}, len_tag={len_tag})"
87
-
88
- def pformat(self):
89
- return str(self.struct)
90
-
91
-
92
- def _parse_wf_preview(tag):
93
- n = len(tag.entries)
94
- wf = np.zeros(n, dtype=np.int8)
95
- col = np.zeros(n, dtype=np.int8)
96
- for i in range(n):
97
- data = tag.entries[i]
98
- wf[i] = data & 0x1F
99
- col[i] = data >> 5
100
- return wf, col
101
-
102
-
103
- class PQTZAnlzTag(AbstractAnlzTag):
104
- """Beat grid struct handler."""
105
-
106
- type = "PQTZ"
107
- name = "beat_grid"
108
- LEN_HEADER = 24
109
-
110
- @property
111
- def count(self):
112
- return len(self.content.entries)
113
-
114
- @property
115
- def beats(self):
116
- return self.get_beats()
117
-
118
- @property
119
- def bpms(self):
120
- return self.get_bpms()
121
-
122
- @property
123
- def bpms_average(self):
124
- if len(self.content.entries):
125
- return np.mean(self.get_bpms())
126
- return 0.0
127
-
128
- @property
129
- def bpms_unique(self):
130
- return np.unique(self.get_bpms())
131
-
132
- @property
133
- def times(self):
134
- return self.get_times()
135
-
136
- def get(self):
137
- n = len(self.content.entries)
138
- beats = np.zeros(n, dtype=np.int8)
139
- bpms = np.zeros(n, dtype=np.float64)
140
- times = np.zeros(n, dtype=np.float64)
141
- for i, entry in enumerate(self.content.entries):
142
- _, beat, bpm, time = entry.values()
143
- beats[i] = beat
144
- bpms[i] = bpm / 100 # BPM is saved as 100 * BPM
145
- times[i] = time / 1_000 # Convert milliseconds to seconds
146
- return beats, bpms, times
147
-
148
- def get_beats(self):
149
- return np.array([entry.beat for entry in self.content.entries], dtype=np.int8)
150
-
151
- def get_bpms(self):
152
- return np.array([entry.tempo / 100 for entry in self.content.entries])
153
-
154
- def get_times(self):
155
- return np.array([entry.time / 1000 for entry in self.content.entries])
156
-
157
- def set(self, beats, bpms, times):
158
- n = len(self.content.entries)
159
- n_beats = len(beats)
160
- n_bpms = len(bpms)
161
- n_times = len(times)
162
- if n_bpms != n_beats:
163
- raise ValueError(
164
- f"Number of bpms not equal to number of beats: {n_bpms} != {n_beats}"
165
- )
166
- if n_times != n_beats:
167
- raise ValueError(
168
- f"Number of times not equal to number of beats: {n_bpms} != {n_times}"
169
- )
170
-
171
- # For now only values of existing beats can be set
172
- if n_beats != n:
173
- raise ValueError(
174
- f"Number of beats not equal to current content length: {n_beats} != {n}"
175
- )
176
-
177
- for i, (beat, bpm, t) in enumerate(zip(beats, bpms, times)):
178
- data = {"beat": int(beat), "tempo": int(100 * bpm), "time": int(1000 * t)}
179
- self.content.entries[i].update(data)
180
-
181
- def set_beats(self, beats):
182
- n = len(self.content.entries)
183
- n_new = len(beats)
184
- if n_new != n:
185
- raise ValueError(
186
- f"Number of beats not equal to current content length: {n_new} != {n}"
187
- )
188
-
189
- for i, beat in enumerate(beats):
190
- self.content.entries[i].beat = beat
191
-
192
- def set_bpms(self, bpms):
193
- n = len(self.content.entries)
194
- n_new = len(bpms)
195
- if n_new != n:
196
- raise ValueError(
197
- f"Number of bpms not equal to current content length: {n_new} != {n}"
198
- )
199
-
200
- for i, bpm in enumerate(bpms):
201
- self.content.entries[i].tempo = int(bpm * 100)
202
-
203
- def set_times(self, times):
204
- n = len(self.content.entries)
205
- n_new = len(times)
206
- if n_new != n:
207
- raise ValueError(
208
- f"Number of times not equal to current content length: {n_new} != {n}"
209
- )
210
-
211
- for i, t in enumerate(times):
212
- self.content.entries[i].time = int(1000 * t)
213
-
214
- def check_parse(self):
215
- assert self.struct.content.entry_count == len(self.struct.content.entries)
216
-
217
- def update_len(self):
218
- self.struct.len_tag = self.struct.len_header + 8 * len(self.content.entries)
219
-
220
-
221
- class PQT2AnlzTag(AbstractAnlzTag):
222
- """Extended (nxs2) beat grid struct handler."""
223
-
224
- type = "PQT2"
225
- name = "beat_grid2"
226
- LEN_HEADER = 56
227
-
228
- count = 2
229
-
230
- @property
231
- def beats(self):
232
- return self.get_beats()
233
-
234
- @property
235
- def bpms(self):
236
- return self.get_bpms()
237
-
238
- @property
239
- def times(self):
240
- return self.get_times()
241
-
242
- @property
243
- def beat_grid_count(self):
244
- return self.content.entry_count
245
-
246
- @property
247
- def bpms_unique(self):
248
- return np.unique(self.get_bpms())
249
-
250
- def check_parse(self):
251
- len_beats = self.struct.content.entry_count
252
- if len_beats:
253
- expected = self.struct.len_tag - self.struct.len_header
254
- actual = 2 * len(self.content.entries) # each entry consist of 2 bytes
255
- assert actual == expected, f"{actual} != {expected}"
256
-
257
- def get(self):
258
- n = len(self.content.bpm)
259
- beats = np.zeros(n, dtype=np.int8)
260
- bpms = np.zeros(n, dtype=np.float64)
261
- times = np.zeros(n, dtype=np.float64)
262
- for i, entry in enumerate(self.content.bpm):
263
- _, beat, bpm, time = entry.values()
264
- beats[i] = beat
265
- bpms[i] = bpm / 100 # BPM is saved as 100 * BPM
266
- times[i] = time / 1_000 # Convert milliseconds to seconds
267
- return beats, bpms, times
268
-
269
- def get_beats(self):
270
- return np.array([entry.beat for entry in self.content.bpm], dtype=np.int8)
271
-
272
- def get_bpms(self):
273
- return np.array([entry.tempo / 100 for entry in self.content.bpm])
274
-
275
- def get_times(self):
276
- return np.array([entry.time / 1000 for entry in self.content.bpm])
277
-
278
- def get_beat_grid(self):
279
- return np.array([entry.beat for entry in self.content.entries], dtype=np.int8)
280
-
281
- def set_beats(self, beats):
282
- for i, beat in enumerate(beats):
283
- self.content.bpm[i].beat = beat
284
-
285
- def set_bpms(self, bpms):
286
- for i, bpm in enumerate(bpms):
287
- self.content.bpm[i].bpm = int(bpm * 100)
288
-
289
- def set_times(self, times):
290
- for i, t in enumerate(times):
291
- self.content.bpm[i].time = int(1000 * t)
292
-
293
- def build(self):
294
- data = structs.AnlzTag.build(self.struct)
295
- if self.struct.content.entry_count == 0:
296
- data = data[: self.struct.len_tag]
297
-
298
- len_data = len(data)
299
- if len_data != self.struct.len_tag:
300
- raise BuildTagLengthError(self.struct, len_data)
301
- return data
302
-
303
-
304
- class PCOBAnlzTag(AbstractAnlzTag):
305
- """Cue list struct handler."""
306
-
307
- type = "PCOB"
308
- name = "cue_list"
309
- LEN_HEADER = 24
310
-
311
-
312
- class PCO2AnlzTag(AbstractAnlzTag):
313
- """Extended (nxs2) cue list struct handler."""
314
-
315
- type = "PCO2"
316
- name = "cue_list2"
317
- LEN_HEADER = 20
318
-
319
-
320
- class PPTHAnlzTag(AbstractAnlzTag):
321
- """Path struct handler."""
322
-
323
- type = "PPTH"
324
- name = "path"
325
- LEN_HEADER = 16
326
-
327
- @property
328
- def path(self):
329
- return self.content.path
330
-
331
- def get(self):
332
- return self.content.path
333
-
334
- def set(self, path):
335
- path = path.replace("\\", "/")
336
- len_path = len(path.encode("utf-16-be")) + 2
337
- self.content.path = path
338
- self.content.len_path = len_path
339
-
340
- def update_len(self):
341
- self.struct.len_tag = self.struct.len_header + self.content.len_path
342
-
343
-
344
- class PVBRAnlzTag(AbstractAnlzTag):
345
- """VBR struct handler."""
346
-
347
- type = "PVBR"
348
- name = "vbr"
349
- LEN_HEADER = 16
350
- LEN_TAG = 1620
351
-
352
- def get(self):
353
- return np.array(self.content.idx)
354
-
355
-
356
- class PSSIAnlzTag(AbstractAnlzTag):
357
- """Song structure struct handler."""
358
-
359
- type = "PSSI"
360
- name = "structure"
361
- LEN_HEADER = 32
362
-
363
-
364
- class PWAVAnlzTag(AbstractAnlzTag):
365
- """Waveform preview struct handler."""
366
-
367
- type = "PWAV"
368
- name = "wf_preview"
369
- LEN_HEADER = 20
370
-
371
- def get(self):
372
- return _parse_wf_preview(self.content)
373
-
374
-
375
- class PWV2AnlzTag(AbstractAnlzTag):
376
- """Tiny waveform preview struct handler."""
377
-
378
- type = "PWV2"
379
- name = "wf_tiny_preview"
380
- LEN_HEADER = 20
381
-
382
- def get(self):
383
- return _parse_wf_preview(self.content)
384
-
385
-
386
- class PWV3AnlzTag(AbstractAnlzTag):
387
- """Waveform detail struct handler."""
388
-
389
- type = "PWV3"
390
- name = "wf_detail"
391
- LEN_HEADER = 24
392
-
393
- def get(self):
394
- return _parse_wf_preview(self.content)
395
-
396
-
397
- class PWV4AnlzTag(AbstractAnlzTag):
398
- """Waveform color preview struct handler."""
399
-
400
- type = "PWV4"
401
- name = "wf_color"
402
- LEN_HEADER = 24
403
-
404
- def get(self):
405
- num_entries = self.content.len_entries
406
- data = self.content.entries
407
- ws, hs = 1, 1
408
- w = int(num_entries / ws)
409
- # parse color and blue waveforms
410
- col_color = np.zeros((num_entries, 2, 3), dtype=np.int64)
411
- col_blues = np.zeros((num_entries, 2, 3), dtype=np.int64)
412
- heights = np.zeros((num_entries, 2), dtype=np.int64)
413
- for x in range(w):
414
- # d0 = data[x * ws * 6 + 0] # unknown?
415
- d1 = data[x * ws * 6 + 1] # some kind of luminance boost?
416
- d2 = data[x * ws * 6 + 2] & 0x7F # inverse intensity for blue waveform
417
- d3 = data[x * ws * 6 + 3] & 0x7F # red
418
- d4 = data[x * ws * 6 + 4] & 0x7F # green
419
- d5 = data[x * ws * 6 + 5] & 0x7F # blue and height of front waveform
420
- bh = int(max(d2, d3, d4) / hs) # back height is max of d3, d4 probably d2?
421
- fh = int(d5 / hs) # front height is d5
422
- fl = 32 # front luminosity increase (arbitrary)
423
- # waveform heights
424
- heights[x] = fh, bh
425
- # color waveform
426
- col = np.array([d3, d4, d5]) * (d1 / 127)
427
- col_color[x] = col, col + fl
428
- # blue waveform
429
- col = 95 - d2 * np.array([1.0, 0.5, 0.25])
430
- col_blues[x] = col, col + fl
431
- return heights, col_color, col_blues
432
-
433
-
434
- class PWV5AnlzTag(AbstractAnlzTag):
435
- """Waveform color detail struct handler."""
436
-
437
- type = "PWV5"
438
- name = "wf_color_detail"
439
- LEN_HEADER = 24
440
-
441
- def get(self):
442
- """Parse the Waveform Color Detail Tag (PWV5)
443
-
444
- The format of the entries is:
445
-
446
- f e d c b a 9 8 7 6 5 4 3 2 1 0
447
- │ red │ green │ blue │ height │00 00│
448
- """
449
- rmask = 0xE000 # 111 000 000 00000 00
450
- gmask = 0x1C00 # 000 111 000 00000 00
451
- bmask = 0x380 # 000 000 111 00000 00
452
- hmask = 0x7C # 000 000 000 11111 00
453
-
454
- n = self.content.len_entries
455
- heights = np.zeros(n, dtype=np.int64)
456
- colors = np.zeros((n, 3), dtype=np.int64)
457
- for i, x in enumerate(self.content.entries):
458
- red = (x & rmask) >> 12
459
- green = (x & gmask) >> 10
460
- blue = (x & bmask) >> 7
461
- heights[i] = (x & hmask) >> 2
462
- colors[i] = red, green, blue
463
- # Normalize heights to 1:
464
- heights = heights / 31
465
- return heights, colors
466
-
467
-
468
- class PWV6AnlzTag(AbstractAnlzTag):
469
- """PWV6 struct handler."""
470
-
471
- type = "PWV6"
472
- name = type
473
- LEN_HEADER = 20
474
-
475
-
476
- class PWV7AnlzTag(AbstractAnlzTag):
477
- """PWV7 struct handler."""
478
-
479
- type = "PWV7"
480
- name = type
481
- LEN_HEADER = 24
482
-
483
-
484
- class PWVCAnlzTag(AbstractAnlzTag):
485
- """PWVC struct handler."""
486
-
487
- type = "PWVC"
488
- name = type
489
- LEN_HEADER = 14
490
-
491
-
492
- TAGS = {
493
- "PQTZ": PQTZAnlzTag,
494
- "PQT2": PQT2AnlzTag,
495
- "PCOB": PCOBAnlzTag, # seen in both DAT and EXT files
496
- "PCO2": PCO2AnlzTag, # seen in EXT files
497
- "PPTH": PPTHAnlzTag,
498
- "PVBR": PVBRAnlzTag,
499
- "PSSI": PSSIAnlzTag, # seen in EXT files
500
- "PWAV": PWAVAnlzTag,
501
- "PWV2": PWV2AnlzTag,
502
- "PWV3": PWV3AnlzTag, # seen in EXT files
503
- "PWV4": PWV4AnlzTag, # seen in EXT files
504
- "PWV5": PWV5AnlzTag, # seen in EXT files
505
- "PWV6": PWV6AnlzTag, # seen in 2EX files
506
- "PWV7": PWV7AnlzTag, # seen in 2EX files
507
- "PWVC": PWVCAnlzTag, # seen in 2EX files
508
- }