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,186 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Author: Dylan Jones
3
- # Date: 2023-02-01
4
-
5
- import logging
6
- from collections import abc
7
- from pathlib import Path
8
- from typing import Union
9
- from .tags import TAGS
10
- from . import structs
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
-
15
- class BuildFileLengthError(Exception):
16
- def __init__(self, struct, len_data):
17
- super().__init__(
18
- f"`len_file` ({struct.len_file}) of '{struct.type}' does not "
19
- f"match the data-length ({len_data})!"
20
- )
21
-
22
-
23
- class AnlzFile(abc.Mapping):
24
- """Rekordbox `ANLZnnnn.xxx` binary file handler."""
25
-
26
- def __init__(self):
27
- self._path = ""
28
- self.file_header = None
29
- self.tags = list()
30
-
31
- @property
32
- def num_tags(self):
33
- return len(self.tags)
34
-
35
- @property
36
- def tag_types(self):
37
- return [tag.type for tag in self.tags]
38
-
39
- @property
40
- def path(self):
41
- return self._path
42
-
43
- @classmethod
44
- def parse(cls, data: bytes):
45
- """Parses the in-memory data of a Rekordbox analysis binary file.
46
-
47
- Parameters
48
- ----------
49
- data : bytes
50
- The in-memory binary contents of a Rekordbox analysis file.
51
-
52
- Returns
53
- -------
54
- self : AnlzFile
55
- The new instance with the parsed file content.
56
- """
57
- self = cls()
58
- self._parse(data)
59
- return self
60
-
61
- @classmethod
62
- def parse_file(cls, path: Union[str, Path]):
63
- """Reads and parses a Rekordbox analysis binary file.
64
-
65
- Parameters
66
- ----------
67
- path : str or Path
68
- The path of a Rekordbox analysis file which is used to read
69
- the file contents before parsing the binary data.
70
-
71
- Returns
72
- -------
73
- self : AnlzFile
74
- The new instance with the parsed file content.
75
-
76
- See Also
77
- --------
78
- AnlzFile.parse: Parses the data of a Rekordbox analysis file.
79
- """
80
- path = Path(path)
81
- ext = path.suffix.upper()
82
- if ext not in (".DAT", ".EXT", ".2EX"):
83
- raise ValueError(f"File type '{ext}' not supported!")
84
-
85
- logger.debug(f"Reading file {path.name}")
86
- with open(path, "rb") as fh:
87
- data = fh.read()
88
-
89
- self = cls.parse(data)
90
- self._path = path
91
- return self
92
-
93
- def _parse(self, data: bytes):
94
- file_header = structs.AnlzFileHeader.parse(data)
95
- tag_type = file_header.type
96
- assert tag_type == "PMAI"
97
-
98
- tags = list()
99
- i = file_header.len_header
100
- while i < file_header.len_file:
101
- # Get data starting from struct
102
- tag_data = data[i:]
103
- # Get the four byte struct type
104
- tag_type = tag_data[:4].decode("ascii")
105
-
106
- try:
107
- # Parse the struct
108
- tag = TAGS[tag_type](tag_data)
109
- tags.append(tag)
110
- len_header = tag.struct.len_header
111
- len_tag = tag.struct.len_tag
112
- logger.debug(
113
- "Parsed struct '%s' (len_header=%s, len_tag=%s)",
114
- tag_type,
115
- len_header,
116
- len_tag,
117
- )
118
- except KeyError:
119
- logger.warning("Tag '%s' not supported!", tag_type)
120
- tag = structs.AnlzTag.parse(tag_data)
121
- len_tag = tag.len_tag
122
- i += len_tag
123
-
124
- self.file_header = file_header
125
- self.tags = tags
126
-
127
- def update_len(self):
128
- # Update struct lengths
129
- tags_len = 0
130
- for tag in self.tags:
131
- tag.update_len()
132
- tags_len += tag.struct.len_tag
133
- # Update file length
134
- len_file = self.file_header.len_header + tags_len
135
- self.file_header.len_file = len_file
136
-
137
- def build(self):
138
- self.update_len()
139
- header_data = structs.AnlzFileHeader.build(self.file_header)
140
- section_data = b"".join(tag.build() for tag in self.tags)
141
- data = header_data + section_data
142
- # Check `len_file`
143
- len_file = self.file_header.len_file
144
- len_data = len(data)
145
- if len_file != len_data:
146
- raise BuildFileLengthError(self.file_header, len_file)
147
-
148
- return data
149
-
150
- def save(self, path=""):
151
- path = path or self._path
152
-
153
- data = self.build()
154
- with open(path, "wb") as fh:
155
- fh.write(data)
156
-
157
- def get_tag(self, key):
158
- return self.__getitem__(key)[0]
159
-
160
- def getall_tags(self, key):
161
- return self.__getitem__(key)
162
-
163
- def get(self, key):
164
- return self.__getitem__(key)[0].get()
165
-
166
- def getall(self, key):
167
- return [tag.get() for tag in self.__getitem__(key)]
168
-
169
- def __len__(self):
170
- return len(self.keys())
171
-
172
- def __iter__(self):
173
- return iter(set(tag.type for tag in self.tags))
174
-
175
- def __getitem__(self, item):
176
- if item.isupper() and len(item) == 4:
177
- return [tag for tag in self.tags if tag.type == item]
178
- else:
179
- return [tag for tag in self.tags if tag.name == item]
180
-
181
- def __repr__(self):
182
- return f"{self.__class__.__name__}({self.tag_types})"
183
-
184
- def set_path(self, path):
185
- tag = self.get_tag("PPTH")
186
- tag.set(path)
@@ -1,299 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Author: Dylan Jones
3
- # Date: 2022-10-24
4
-
5
- """Binary structures of Rekordbox ANLZ-files.
6
-
7
- References
8
- ----------
9
- .. [1] Rekordbox Export Structure Analysis: Analysis Files,
10
- https://djl-analysis.deepsymmetry.org/rekordbox-export-analysis/anlz.html
11
- """
12
-
13
- from construct import Int8ub, Int16ub, Int32ub, PaddedString, Int32sb, StopIf
14
- from construct import Const, Array, Padding, Bytes
15
- from construct import Default, Enum, GreedyRange, Struct, Switch, this
16
-
17
-
18
- # -- Beat Grid Tag (PQTZ) --------------------------------------------------------------
19
-
20
- AnlzQuantizeTick = Struct(
21
- "beat" / Int16ub,
22
- "tempo" / Int16ub,
23
- "time" / Int32ub # in ms from start
24
- )
25
-
26
- # len_header: 24
27
- PQTZ = Struct(
28
- "u1" / Padding(4),
29
- "u2" / Const(0x80000, Int32ub),
30
- "entry_count" / Int32ub,
31
- "entries" / Array(this.entry_count, AnlzQuantizeTick),
32
- )
33
-
34
-
35
- # Extended Beat Grid Tag (PQT2)
36
-
37
- AnlzQuantizeTick2 = Struct(
38
- "beat" / Int8ub, # 1 byte
39
- "unkown" / Int8ub, # 1 byte
40
- )
41
-
42
- # len_header: 56
43
- PQT2 = Struct(
44
- Padding(4), # -> 16
45
- "u1" / Const(0x01000002, Int32ub), # -> 20, count of "bpm" objects?
46
- Padding(4), # -> 24
47
- "bpm" / Array(2, AnlzQuantizeTick), # -> 40 (2 * 8 bytes = 16 bytes)
48
- "entry_count" / Int32ub, # -> 44 number of entries of 2 bytes
49
- "u3" / Int32ub, # -> 48
50
- "u4" / Int32ub, # -> 52
51
- "u5" / Int32ub, # -> 56
52
- # End header
53
- StopIf(this.entry_count == 0),
54
- # "entries" / Array(this.entry_count, Bytes(2)),
55
- "entries" / Array(this.entry_count, AnlzQuantizeTick2),
56
- )
57
-
58
-
59
- # -- Cue List Tag (PCOB) ---------------------------------------------------------------
60
-
61
- AnlzCuePointType = Enum(Int8ub, single=1, loop=2)
62
-
63
- AnlzCuePointStatus = Enum(Int32ub, disabled=0, enabled=4)
64
-
65
- AnlzTagCueObjectType = Enum(Int32ub, memory=0, hotcue=1)
66
-
67
- AnlzCuePoint = Struct(
68
- "type" / Const("PCPT", PaddedString(4, encoding="ascii")),
69
- "len_header" / Int32ub,
70
- "len_entry" / Int32ub,
71
- "hot_cue" / Int32ub, # 0 for memory
72
- "status" / AnlzCuePointStatus,
73
- "u1" / Const(0x10000, Int32ub),
74
- "order_first" / Int16ub, # 0xffff for first cue, 0,1,3 for next
75
- "order_last" / Int16ub, # 1,2,3 for first, second, third cue, 0xffff for last
76
- "type" / AnlzCuePointType,
77
- Padding(1),
78
- "u2" / Const(1000, Int16ub),
79
- "time" / Int32ub,
80
- "loop_time" / Default(Int32ub, -1),
81
- Padding(16),
82
- )
83
-
84
- # len_header: 24
85
- PCOB = Struct(
86
- "cue_type" / AnlzTagCueObjectType,
87
- "unk" / Int16ub,
88
- "count" / Int16ub,
89
- "memory_count" / Int32sb,
90
- "entries" / Array(this.count, AnlzCuePoint),
91
- )
92
-
93
-
94
- # Extended (nxs2) Cue List Tag (PCO2)
95
-
96
- AnlzCuePoint2 = Struct(
97
- "type" / Const("PCP2", PaddedString(4, encoding="ascii")),
98
- "len_header" / Int32ub,
99
- "len_entry" / Int32ub,
100
- "hot_cue" / Int32ub, # 0 for memory
101
- "type" / Int8ub, # spotted: 0x010003e8 0x020003e8
102
- Padding(3),
103
- "time" / Int32ub,
104
- "loop_time" / Default(Int32ub, -1),
105
- "color_id" / Int8ub,
106
- Padding(7),
107
- "loop_enumerator" / Int16ub,
108
- "loop_denominator" / Int16ub,
109
- "len_comment" / Int32ub,
110
- "comment" / PaddedString(this.len_comment, encoding="utf-16-be"),
111
- "color_code" / Int8ub,
112
- "color_red" / Int8ub,
113
- "color_green" / Int8ub,
114
- "color_blue" / Int8ub,
115
- Padding(this.len_entry - 48 - this.len_comment),
116
- )
117
-
118
- # len_header: 20
119
- PCO2 = Struct(
120
- "type" / AnlzTagCueObjectType,
121
- "count" / Int16ub,
122
- "unknown" / Int16ub,
123
- "entries" / Array(this.count, AnlzCuePoint2),
124
- )
125
-
126
-
127
- # -- Path Tag (PPTH) -------------------------------------------------------------------
128
-
129
- # len_header: 16
130
- PPTH = Struct(
131
- "len_path" / Int32ub, # is 0 for some tag types
132
- "path" / PaddedString(this.len_path - 2, encoding="utf-16-be"),
133
- Padding(2),
134
- )
135
-
136
-
137
- # -- VBR Tag (PVBR) --------------------------------------------------------------------
138
-
139
- # len_header: 16
140
- PVBR = Struct(
141
- "u1" / Int32ub,
142
- "idx" / Array(400, Int32ub),
143
- "u2" / Int32ub
144
- )
145
-
146
-
147
- # -- (Tiny) Waveform Preview Tag (PWAV / PWV2) -----------------------------------------
148
-
149
- # len_header: 20
150
- PWAV = Struct(
151
- "len_preview" / Int32ub, # is 0 for some tag types
152
- "unknown" / Const(0x10000, Int32ub),
153
- "entries" / Array(this.len_preview, Int8ub),
154
- )
155
-
156
- # len_header: 20
157
- PWV2 = Struct(
158
- "len_preview" / Int32ub, # is 0 for some tag types
159
- "unknown" / Const(0x10000, Int32ub),
160
- "entries" / Array(this.len_preview, Int8ub),
161
- )
162
-
163
-
164
- # -- Waveform Detail Tag (PWV3) --------------------------------------------------------
165
-
166
- # len_header: 24
167
- PWV3 = Struct(
168
- "len_entry_bytes" / Const(1, Int32ub),
169
- "len_entries" / Int32ub,
170
- "u1" / Const(0x00960000, Int32ub),
171
- "entries" / Array(this.len_entries, Int8ub),
172
- )
173
-
174
-
175
- # -- Waveform Color Preview Tag (PWV4) -------------------------------------------------
176
-
177
- # len_header: 24
178
- PWV4 = Struct(
179
- "len_entry_bytes" / Const(0x00000006, Int32ub),
180
- "len_entries" / Int32ub,
181
- "unknown" / Int32ub,
182
- "entries" / Bytes(this.len_entry_bytes * this.len_entries),
183
- )
184
-
185
-
186
- # -- Waveform Color Detail Tag (PWV5) --------------------------------------------------
187
-
188
- # len_header: 24
189
- PWV5 = Struct(
190
- "len_entry_bytes" / Const(0x00000002, Int32ub),
191
- "len_entries" / Int32ub,
192
- "unknown" / Int32ub,
193
- "entries" / Array(this.len_entries, Int16ub),
194
- )
195
-
196
- # -- Song Structure Tag (PSSI) ---------------------------------------------------------
197
-
198
- # FixMe: Implement reverse XOR mask to descramble values
199
-
200
- SongStructureEntry = Struct(
201
- "index" / Int16ub,
202
- "beat" / Int16ub,
203
- "kind" / Int16ub,
204
- "u1" / Int8ub,
205
- "k1" / Int8ub,
206
- "u2" / Int8ub,
207
- "k2" / Int8ub,
208
- "u3" / Int8ub,
209
- "b" / Int8ub,
210
- "beat_2" / Int16ub,
211
- "beat_3" / Int16ub,
212
- "beat_4" / Int16ub,
213
- "u4" / Int8ub,
214
- "k3" / Int8ub,
215
- "u5" / Int8ub,
216
- "fill" / Int8ub,
217
- "beat_fill" / Int16ub,
218
- )
219
-
220
- # len_header: 32
221
- PSSI = Struct(
222
- "len_entry_bytes" / Const(24, Int32ub),
223
- "len_entries" / Int16ub,
224
- "mood" / Bytes(2),
225
- "u1" / Bytes(6),
226
- "end_beat" / Bytes(2),
227
- "u2" / Bytes(2),
228
- "bank" / Bytes(1),
229
- "u3" / Bytes(1),
230
- "entries" / Array(this.len_entries, SongStructureEntry),
231
- )
232
-
233
- # -- PWV6 ------------------------------------------------------------------------------
234
-
235
- # len_header: 20
236
- PWV6 = Struct(
237
- "len_entry_bytes" / Const(0x00000003, Int32ub),
238
- "len_entries" / Int32ub,
239
- "entries" / Bytes(this.len_entry_bytes * this.len_entries),
240
- )
241
-
242
- # -- PWV7 ------------------------------------------------------------------------------
243
-
244
- # len_header: 24
245
- PWV7 = Struct(
246
- "len_entry_bytes" / Const(0x00000003, Int32ub),
247
- "len_entries" / Int32ub,
248
- "unknown" / Const(0x00960000, Int32ub),
249
- "entries" / Bytes(this.len_entry_bytes * this.len_entries),
250
- )
251
-
252
- # -- PWVC ------------------------------------------------------------------------------
253
-
254
- # len_header: 14
255
- PWVC = Struct(
256
- "unknown" / Int16ub,
257
- "data" / Array(3, Int16ub)
258
- )
259
-
260
-
261
- # -- Main Items ------------------------------------------------------------------------
262
-
263
- AnlzFileHeader = Struct(
264
- "type" / PaddedString(4, encoding="ascii"),
265
- "len_header" / Int32ub,
266
- "len_file" / Int32ub,
267
- "u1" / Int32ub,
268
- "u2" / Int32ub,
269
- "u3" / Int32ub,
270
- "u4" / Int32ub,
271
- )
272
-
273
-
274
- AnlzTag = Struct(
275
- "type" / PaddedString(4, encoding="ascii"),
276
- "len_header" / Int32ub,
277
- "len_tag" / Int32ub,
278
- "content" / Switch(
279
- this.type,
280
- {
281
- "PQTZ": PQTZ,
282
- "PQT2": PQT2,
283
- "PCOB": PCOB, # seen in both DAT and EXT files
284
- "PCO2": PCO2, # seen in EXT files
285
- "PPTH": PPTH,
286
- "PVBR": PVBR,
287
- "PSSI": PSSI, # seen in EXT files
288
- "PWAV": PWAV,
289
- "PWV2": PWV2,
290
- "PWV3": PWV3, # seen in EXT files
291
- "PWV4": PWV4, # seen in EXT files
292
- "PWV5": PWV5, # seen in EXT files
293
- "PWV6": PWV6, # seen in 2EX files
294
- "PWV7": PWV7, # seen in 2EX files
295
- "PWVC": PWVC, # seen in 2EX files
296
- },
297
- default=Bytes(this.len_tag - 12),
298
- ),
299
- )