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/db6/tables.py
CHANGED
@@ -7,15 +7,15 @@
|
|
7
7
|
import math
|
8
8
|
import re
|
9
9
|
import struct
|
10
|
-
from datetime import datetime
|
10
|
+
from datetime import datetime, timezone
|
11
11
|
from enum import IntEnum
|
12
|
-
from typing import List
|
12
|
+
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
13
13
|
|
14
14
|
import numpy as np
|
15
15
|
from sqlalchemy import (
|
16
16
|
VARCHAR,
|
17
17
|
BigInteger,
|
18
|
-
|
18
|
+
Dialect,
|
19
19
|
Float,
|
20
20
|
ForeignKey,
|
21
21
|
Integer,
|
@@ -71,6 +71,7 @@ __all__ = [
|
|
71
71
|
"ImageFile",
|
72
72
|
"SettingFile",
|
73
73
|
"UuidIDMap",
|
74
|
+
"FileType",
|
74
75
|
]
|
75
76
|
|
76
77
|
|
@@ -116,13 +117,11 @@ TABLES = [
|
|
116
117
|
|
117
118
|
|
118
119
|
def datetime_to_str(value: datetime) -> str:
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
else:
|
125
|
-
s = s[:-3] + " +00:00"
|
120
|
+
# Convert to UTC timezone string
|
121
|
+
s = value.astimezone(timezone.utc).isoformat().replace("T", " ")
|
122
|
+
# Get the timezone info (last 6 characters of the string)
|
123
|
+
tzinfo = s[-6:]
|
124
|
+
s = s[:-9] + " " + tzinfo
|
126
125
|
return s
|
127
126
|
|
128
127
|
|
@@ -146,7 +145,7 @@ def string_to_datetime(value: str) -> datetime:
|
|
146
145
|
return dt.astimezone().replace(tzinfo=None)
|
147
146
|
|
148
147
|
|
149
|
-
class DateTime(TypeDecorator):
|
148
|
+
class DateTime(TypeDecorator): # type: ignore[type-arg]
|
150
149
|
"""Custom datetime column with timezone support.
|
151
150
|
|
152
151
|
The datetime format in the database is `YYYY-MM-DD HH:MM:SS.SSS +00:00`.
|
@@ -156,10 +155,10 @@ class DateTime(TypeDecorator):
|
|
156
155
|
impl = Text
|
157
156
|
cache_ok = True
|
158
157
|
|
159
|
-
def process_bind_param(self, value: datetime, dialect) -> str:
|
158
|
+
def process_bind_param(self, value: datetime, dialect: Dialect) -> str: # type: ignore[override]
|
160
159
|
return datetime_to_str(value)
|
161
160
|
|
162
|
-
def process_result_value(self, value: str, dialect):
|
161
|
+
def process_result_value(self, value: str, dialect: Dialect) -> Optional[datetime]: # type: ignore[override]
|
163
162
|
if value:
|
164
163
|
return string_to_datetime(value)
|
165
164
|
return None
|
@@ -190,65 +189,65 @@ class Base(DeclarativeBase):
|
|
190
189
|
__keys__: List[str] = []
|
191
190
|
|
192
191
|
@classmethod
|
193
|
-
def create(cls, **kwargs):
|
192
|
+
def create(cls, **kwargs: Any): # type: ignore # noqa: ANN206
|
194
193
|
with RekordboxAgentRegistry.disabled():
|
195
194
|
# noinspection PyArgumentList
|
196
195
|
self = cls(**kwargs)
|
197
196
|
return self
|
198
197
|
|
199
198
|
@classmethod
|
200
|
-
def columns(cls):
|
199
|
+
def columns(cls) -> List[str]:
|
201
200
|
"""Returns a list of all column names without the relationships."""
|
202
201
|
return [column.name for column in inspect(cls).c]
|
203
202
|
|
204
203
|
@classmethod
|
205
|
-
def relationships(cls):
|
204
|
+
def relationships(cls) -> List[str]:
|
206
205
|
"""Returns a list of all relationship names."""
|
207
206
|
return [column.key for column in inspect(cls).relationships] # noqa
|
208
207
|
|
209
208
|
@classmethod
|
210
|
-
def __get_keys__(cls): # pragma: no cover
|
209
|
+
def __get_keys__(cls) -> List[str]: # pragma: no cover
|
211
210
|
"""Get all attributes of the table."""
|
212
211
|
items = cls.__dict__.items()
|
213
212
|
keys = [k for k, v in items if not callable(v) and not k.startswith("_")]
|
214
213
|
return keys
|
215
214
|
|
216
215
|
@classmethod
|
217
|
-
def keys(cls): # pragma: no cover
|
216
|
+
def keys(cls) -> List[str]: # pragma: no cover
|
218
217
|
"""Returns a list of all column names including the relationships."""
|
219
218
|
if not cls.__keys__: # Cache the keys
|
220
219
|
cls.__keys__ = cls.__get_keys__()
|
221
220
|
return cls.__keys__
|
222
221
|
|
223
|
-
def __iter__(self):
|
222
|
+
def __iter__(self) -> Iterator[str]:
|
224
223
|
"""Iterates over all columns and relationship names."""
|
225
224
|
return iter(self.keys())
|
226
225
|
|
227
|
-
def __len__(self):
|
226
|
+
def __len__(self) -> int:
|
228
227
|
return sum(1 for _ in self.__iter__())
|
229
228
|
|
230
|
-
def __getitem__(self, item):
|
229
|
+
def __getitem__(self, item: str) -> Any:
|
231
230
|
return self.__getattribute__(item)
|
232
231
|
|
233
232
|
# noinspection PyUnresolvedReferences
|
234
|
-
def __setattr__(self, key, value):
|
233
|
+
def __setattr__(self, key: str, value: Any) -> None:
|
235
234
|
if not key.startswith("_"):
|
236
235
|
RekordboxAgentRegistry.on_update(self, key, value)
|
237
236
|
super().__setattr__(key, value)
|
238
237
|
|
239
|
-
def values(self):
|
238
|
+
def values(self) -> List[Any]:
|
240
239
|
"""Returns a list of all column values including the relationships."""
|
241
240
|
return [self.__getitem__(key) for key in self.keys()]
|
242
241
|
|
243
|
-
def items(self):
|
242
|
+
def items(self) -> Iterator[Tuple[str, Any]]:
|
244
243
|
for key in self.__iter__():
|
245
244
|
yield key, self.__getitem__(key)
|
246
245
|
|
247
|
-
def to_dict(self):
|
246
|
+
def to_dict(self) -> Dict[str, Any]:
|
248
247
|
"""Returns a dictionary of all column names and values."""
|
249
248
|
return {key: self.__getitem__(key) for key in self.columns()}
|
250
249
|
|
251
|
-
def pformat(self, indent=" "): # pragma: no cover
|
250
|
+
def pformat(self, indent: str = " ") -> str: # pragma: no cover
|
252
251
|
lines = [f"{self.__tablename__}"]
|
253
252
|
columns = self.columns()
|
254
253
|
w = max(len(col) for col in columns)
|
@@ -271,7 +270,7 @@ class StatsTime:
|
|
271
270
|
class StatsFull:
|
272
271
|
"""Mixin class for tables that use all statistics columns."""
|
273
272
|
|
274
|
-
ID:
|
273
|
+
ID: Mapped[str]
|
275
274
|
"""The ID (primary key) of the table entry."""
|
276
275
|
|
277
276
|
UUID: Mapped[str] = mapped_column(VARCHAR(255), default=None)
|
@@ -296,7 +295,7 @@ class StatsFull:
|
|
296
295
|
)
|
297
296
|
"""The last update date of the table entry (from :class:`StatsFull`)."""
|
298
297
|
|
299
|
-
def __repr__(self):
|
298
|
+
def __repr__(self) -> str:
|
300
299
|
return f"<{self.__class__.__name__}({self.ID})>"
|
301
300
|
|
302
301
|
|
@@ -525,7 +524,7 @@ class DjmdAlbum(Base, StatsFull):
|
|
525
524
|
AlbumArtistName = association_proxy("AlbumArtist", "Name")
|
526
525
|
"""The name of the album artist (:class:`DjmdArtist`) of the track."""
|
527
526
|
|
528
|
-
def __repr__(self):
|
527
|
+
def __repr__(self) -> str:
|
529
528
|
s = f"{self.ID: <10} Name={self.Name}"
|
530
529
|
return f"<{self.__class__.__name__}({s})>"
|
531
530
|
|
@@ -542,7 +541,7 @@ class DjmdArtist(Base, StatsFull):
|
|
542
541
|
SearchStr: Mapped[str] = mapped_column(VARCHAR(255), default=None)
|
543
542
|
"""The search string of the artist."""
|
544
543
|
|
545
|
-
def __repr__(self):
|
544
|
+
def __repr__(self) -> str:
|
546
545
|
s = f"{self.ID: <10} Name={self.Name}"
|
547
546
|
return f"<{self.__class__.__name__}({s})>"
|
548
547
|
|
@@ -588,7 +587,7 @@ class DjmdColor(Base, StatsFull):
|
|
588
587
|
Commnt: Mapped[str] = mapped_column(VARCHAR(255), default=None)
|
589
588
|
"""The comment (name) of the color."""
|
590
589
|
|
591
|
-
def __repr__(self):
|
590
|
+
def __repr__(self) -> str:
|
592
591
|
s = f"{self.ID: <2} Comment={self.Commnt}"
|
593
592
|
return f"<{self.__class__.__name__}({s})>"
|
594
593
|
|
@@ -807,7 +806,7 @@ class DjmdContent(Base, StatsFull):
|
|
807
806
|
MyTagIDs = association_proxy("MyTags", "MyTagID")
|
808
807
|
"""The IDs of the my tags (:class:`DjmdSongMyTag`) of the track."""
|
809
808
|
|
810
|
-
def __repr__(self):
|
809
|
+
def __repr__(self) -> str:
|
811
810
|
s = f"{self.ID: <10} Title={self.Title}"
|
812
811
|
return f"<{self.__class__.__name__}({s})>"
|
813
812
|
|
@@ -869,11 +868,11 @@ class DjmdCue(Base, StatsFull):
|
|
869
868
|
"""The content entry of the cue point (links to :class:`DjmdContent`)."""
|
870
869
|
|
871
870
|
@property
|
872
|
-
def is_memory_cue(self):
|
871
|
+
def is_memory_cue(self) -> bool:
|
873
872
|
return self.Kind == 0
|
874
873
|
|
875
874
|
@property
|
876
|
-
def is_hot_cue(self):
|
875
|
+
def is_hot_cue(self) -> bool:
|
877
876
|
return self.Kind > 0
|
878
877
|
|
879
878
|
|
@@ -889,7 +888,7 @@ class DjmdDevice(Base, StatsFull):
|
|
889
888
|
Name: Mapped[str] = mapped_column(VARCHAR(255), default=None)
|
890
889
|
"""The name of the device."""
|
891
890
|
|
892
|
-
def __repr__(self):
|
891
|
+
def __repr__(self) -> str:
|
893
892
|
s = f"{self.ID: <2} Name={self.Name}"
|
894
893
|
return f"<{self.__class__.__name__}({s})>"
|
895
894
|
|
@@ -904,7 +903,7 @@ class DjmdGenre(Base, StatsFull):
|
|
904
903
|
Name: Mapped[str] = mapped_column(VARCHAR(255), default=None)
|
905
904
|
"""The name of the genre."""
|
906
905
|
|
907
|
-
def __repr__(self):
|
906
|
+
def __repr__(self) -> str:
|
908
907
|
s = f"{self.ID: <2} Name={self.Name}"
|
909
908
|
return f"<{self.__class__.__name__}({s})>"
|
910
909
|
|
@@ -943,7 +942,7 @@ class DjmdHistory(Base, StatsFull):
|
|
943
942
|
Backrefs to the parent history playlist via :attr:`Parent`.
|
944
943
|
"""
|
945
944
|
|
946
|
-
def __repr__(self):
|
945
|
+
def __repr__(self) -> str:
|
947
946
|
s = f"{self.ID: <2} Name={self.Name}"
|
948
947
|
return f"<{self.__class__.__name__}({s})>"
|
949
948
|
|
@@ -1007,7 +1006,7 @@ class DjmdHotCueBanklist(Base, StatsFull):
|
|
1007
1006
|
Backrefs to the parent hot-cue banklist via :attr:`Parent`.
|
1008
1007
|
"""
|
1009
1008
|
|
1010
|
-
def __repr__(self):
|
1009
|
+
def __repr__(self) -> str:
|
1011
1010
|
s = f"{self.ID: <2} Name={self.Name}"
|
1012
1011
|
return f"<{self.__class__.__name__}({s})>"
|
1013
1012
|
|
@@ -1087,7 +1086,7 @@ class DjmdKey(Base, StatsFull):
|
|
1087
1086
|
Seq: Mapped[int] = mapped_column(Integer, default=None)
|
1088
1087
|
"""The sequence of the key (for ordering)."""
|
1089
1088
|
|
1090
|
-
def __repr__(self):
|
1089
|
+
def __repr__(self) -> str:
|
1091
1090
|
s = f"{self.ID: <2} Name={self.ScaleName}"
|
1092
1091
|
return f"<{self.__class__.__name__}({s})>"
|
1093
1092
|
|
@@ -1102,7 +1101,7 @@ class DjmdLabel(Base, StatsFull):
|
|
1102
1101
|
Name: Mapped[str] = mapped_column(VARCHAR(255), default=None)
|
1103
1102
|
"""The name of the label."""
|
1104
1103
|
|
1105
|
-
def __repr__(self):
|
1104
|
+
def __repr__(self) -> str:
|
1106
1105
|
s = f"{self.ID: <2} Name={self.Name}"
|
1107
1106
|
return f"<{self.__class__.__name__}({s})>"
|
1108
1107
|
|
@@ -1119,7 +1118,7 @@ class DjmdMenuItems(Base, StatsFull):
|
|
1119
1118
|
Name: Mapped[str] = mapped_column(VARCHAR(255), default=None)
|
1120
1119
|
"""The name of the menu item."""
|
1121
1120
|
|
1122
|
-
def __repr__(self):
|
1121
|
+
def __repr__(self) -> str:
|
1123
1122
|
s = f"{self.ID: <2} Name={self.Name}"
|
1124
1123
|
return f"<{self.__class__.__name__}({s})>"
|
1125
1124
|
|
@@ -1151,7 +1150,7 @@ class DjmdMixerParam(Base, StatsFull):
|
|
1151
1150
|
"""The content this mixer parameters belong to (links to :class:`DjmdContent`)."""
|
1152
1151
|
|
1153
1152
|
@staticmethod
|
1154
|
-
def _get_db(low, high):
|
1153
|
+
def _get_db(low: int, high: int) -> float:
|
1155
1154
|
integer = (high << 16) | low
|
1156
1155
|
byte_data = integer.to_bytes(4, byteorder="big")
|
1157
1156
|
factor = struct.unpack("!f", byte_data)[0]
|
@@ -1160,7 +1159,7 @@ class DjmdMixerParam(Base, StatsFull):
|
|
1160
1159
|
return 20 * math.log10(factor)
|
1161
1160
|
|
1162
1161
|
@staticmethod
|
1163
|
-
def _set_db(value):
|
1162
|
+
def _set_db(value: float) -> Tuple[int, int]:
|
1164
1163
|
if value == -np.inf:
|
1165
1164
|
return 0, 0
|
1166
1165
|
factor = 10 ** (value / 20)
|
@@ -1184,7 +1183,7 @@ class DjmdMixerParam(Base, StatsFull):
|
|
1184
1183
|
self.GainLow, self.GainHigh = self._set_db(value)
|
1185
1184
|
|
1186
1185
|
@property
|
1187
|
-
def Peak(self):
|
1186
|
+
def Peak(self) -> float:
|
1188
1187
|
"""The peak amplitude of a track in dB.
|
1189
1188
|
|
1190
1189
|
This value is computed from the low and high peak values.
|
@@ -1193,7 +1192,7 @@ class DjmdMixerParam(Base, StatsFull):
|
|
1193
1192
|
return self._get_db(self.PeakLow, self.PeakHigh)
|
1194
1193
|
|
1195
1194
|
@Peak.setter
|
1196
|
-
def Peak(self, value):
|
1195
|
+
def Peak(self, value: float) -> None:
|
1197
1196
|
self.PeakLow, self.PeakHigh = self._set_db(value)
|
1198
1197
|
|
1199
1198
|
|
@@ -1229,7 +1228,7 @@ class DjmdMyTag(Base, StatsFull):
|
|
1229
1228
|
Backrefs to the parent list via :attr:`Parent`.
|
1230
1229
|
"""
|
1231
1230
|
|
1232
|
-
def __repr__(self):
|
1231
|
+
def __repr__(self) -> str:
|
1233
1232
|
s = f"{self.ID: <2} Name={self.Name}"
|
1234
1233
|
return f"<{self.__class__.__name__}({s})>"
|
1235
1234
|
|
@@ -1300,18 +1299,18 @@ class DjmdPlaylist(Base, StatsFull):
|
|
1300
1299
|
"""
|
1301
1300
|
|
1302
1301
|
@property
|
1303
|
-
def is_playlist(self):
|
1302
|
+
def is_playlist(self) -> bool:
|
1304
1303
|
return self.Attribute == PlaylistType.PLAYLIST
|
1305
1304
|
|
1306
1305
|
@property
|
1307
|
-
def is_folder(self):
|
1306
|
+
def is_folder(self) -> bool:
|
1308
1307
|
return self.Attribute == PlaylistType.FOLDER
|
1309
1308
|
|
1310
1309
|
@property
|
1311
|
-
def is_smart_playlist(self):
|
1310
|
+
def is_smart_playlist(self) -> bool:
|
1312
1311
|
return self.Attribute == PlaylistType.SMART_PLAYLIST
|
1313
1312
|
|
1314
|
-
def __repr__(self):
|
1313
|
+
def __repr__(self) -> str:
|
1315
1314
|
s = f"{self.ID: <2} Name={self.Name}"
|
1316
1315
|
return f"<{self.__class__.__name__}({s})>"
|
1317
1316
|
|
@@ -1381,7 +1380,7 @@ class DjmdRelatedTracks(Base, StatsFull):
|
|
1381
1380
|
Backrefs to the parent related tracks list via :attr:`Parent`.
|
1382
1381
|
"""
|
1383
1382
|
|
1384
|
-
def __repr__(self):
|
1383
|
+
def __repr__(self) -> str:
|
1385
1384
|
s = f"{self.ID: <2} Name={self.Name}"
|
1386
1385
|
return f"<{self.__class__.__name__}({s})>"
|
1387
1386
|
|
@@ -1446,7 +1445,7 @@ class DjmdSampler(Base, StatsFull):
|
|
1446
1445
|
Backrefs to the parent sampler list via :attr:`Parent`.
|
1447
1446
|
"""
|
1448
1447
|
|
1449
|
-
def __repr__(self):
|
1448
|
+
def __repr__(self) -> str:
|
1450
1449
|
s = f"{self.ID: <2} Name={self.Name}"
|
1451
1450
|
return f"<{self.__class__.__name__}({s})>"
|
1452
1451
|
|
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
import re
|
6
6
|
from pathlib import Path
|
7
|
+
from typing import List, Union
|
7
8
|
|
8
9
|
from . import structs
|
9
10
|
from .file import (
|
@@ -18,7 +19,7 @@ from .file import (
|
|
18
19
|
RE_MYSETTING = re.compile(".*SETTING[0-9]?.DAT$")
|
19
20
|
|
20
21
|
|
21
|
-
def get_mysetting_paths(root, deep=False):
|
22
|
+
def get_mysetting_paths(root: Union[str, Path], deep: bool = False) -> List[Path]:
|
22
23
|
files = list()
|
23
24
|
root = Path(root)
|
24
25
|
iteator = root.rglob("*") if deep else root.iterdir()
|
@@ -28,6 +29,6 @@ def get_mysetting_paths(root, deep=False):
|
|
28
29
|
return files
|
29
30
|
|
30
31
|
|
31
|
-
def read_mysetting_file(path) -> SettingsFile:
|
32
|
+
def read_mysetting_file(path: Union[str, Path]) -> SettingsFile:
|
32
33
|
obj = FILES[str(Path(path).name)]
|
33
34
|
return obj.parse_file(path)
|
pyrekordbox/mysettings/file.py
CHANGED
@@ -6,6 +6,8 @@
|
|
6
6
|
|
7
7
|
import re
|
8
8
|
from collections.abc import MutableMapping
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import Any, Dict, Iterator, Type, Union
|
9
11
|
|
10
12
|
from construct import Struct
|
11
13
|
|
@@ -51,7 +53,7 @@ CRC16_XMODEM_TABLE = [
|
|
51
53
|
RE_INVALID_KEY = re.compile("[_u][0-9]?", flags=re.IGNORECASE)
|
52
54
|
|
53
55
|
|
54
|
-
def compute_checksum(data, struct):
|
56
|
+
def compute_checksum(data: bytes, struct: Struct) -> int:
|
55
57
|
"""Computes the CRC16 XModem checksum for My-Setting files.
|
56
58
|
|
57
59
|
The checksum is calculated over the contents of the `data` field,
|
@@ -83,11 +85,11 @@ def compute_checksum(data, struct):
|
|
83
85
|
return crc
|
84
86
|
|
85
87
|
|
86
|
-
def _is_valid_key(k: str):
|
88
|
+
def _is_valid_key(k: str) -> bool:
|
87
89
|
return not RE_INVALID_KEY.match(k)
|
88
90
|
|
89
91
|
|
90
|
-
class SettingsFile(MutableMapping):
|
92
|
+
class SettingsFile(MutableMapping): # type: ignore[type-arg]
|
91
93
|
"""Base class for the Rekordbox My-Setting file handler.
|
92
94
|
|
93
95
|
The base class implements the getters and setter defined by the keys and
|
@@ -98,16 +100,16 @@ class SettingsFile(MutableMapping):
|
|
98
100
|
"""
|
99
101
|
|
100
102
|
struct: Struct
|
101
|
-
defaults:
|
103
|
+
defaults: Dict[str, str]
|
102
104
|
version: str = "" # only used by DEVSETTING
|
103
105
|
|
104
|
-
def __init__(self):
|
106
|
+
def __init__(self) -> None:
|
105
107
|
super().__init__()
|
106
108
|
self.parsed = None
|
107
|
-
self._items = dict()
|
109
|
+
self._items: Dict[str, str] = dict()
|
108
110
|
|
109
111
|
@classmethod
|
110
|
-
def parse(cls, data: bytes):
|
112
|
+
def parse(cls, data: bytes) -> "SettingsFile":
|
111
113
|
"""Parses the in-memory data of a Rekordbox settings binary file.
|
112
114
|
|
113
115
|
Parameters
|
@@ -125,18 +127,18 @@ class SettingsFile(MutableMapping):
|
|
125
127
|
return self
|
126
128
|
|
127
129
|
@classmethod
|
128
|
-
def parse_file(cls, path: str):
|
130
|
+
def parse_file(cls, path: Union[str, Path]) -> "SettingsFile":
|
129
131
|
"""Reads and parses a Rekordbox settings binary file.
|
130
132
|
|
131
133
|
Parameters
|
132
134
|
----------
|
133
|
-
path : str
|
135
|
+
path : str or Path
|
134
136
|
The path of a Rekordbox settings file which is used to read
|
135
137
|
the file contents before parsing the binary data.
|
136
138
|
|
137
139
|
Returns
|
138
140
|
-------
|
139
|
-
self :
|
141
|
+
self : SettingsFile
|
140
142
|
The new instance with the parsed file content.
|
141
143
|
|
142
144
|
See Also
|
@@ -147,7 +149,7 @@ class SettingsFile(MutableMapping):
|
|
147
149
|
data = fh.read()
|
148
150
|
return cls.parse(data)
|
149
151
|
|
150
|
-
def _parse(self, data):
|
152
|
+
def _parse(self, data: bytes) -> None:
|
151
153
|
parsed = self.struct.parse(data)
|
152
154
|
keys = filter(_is_valid_key, parsed.data.keys())
|
153
155
|
items = dict()
|
@@ -157,27 +159,27 @@ class SettingsFile(MutableMapping):
|
|
157
159
|
self.parsed = parsed
|
158
160
|
self._items.update(items)
|
159
161
|
|
160
|
-
def __len__(self):
|
162
|
+
def __len__(self) -> int:
|
161
163
|
return len(self.defaults.keys())
|
162
164
|
|
163
|
-
def __iter__(self):
|
165
|
+
def __iter__(self) -> Iterator[str]:
|
164
166
|
return iter(self.defaults)
|
165
167
|
|
166
|
-
def __getitem__(self, key):
|
168
|
+
def __getitem__(self, key: str) -> str:
|
167
169
|
try:
|
168
170
|
return self._items[key]
|
169
171
|
except KeyError:
|
170
172
|
return self.defaults[key]
|
171
173
|
|
172
|
-
def __setitem__(self, key, value):
|
174
|
+
def __setitem__(self, key: str, value: str) -> None:
|
173
175
|
if key not in self.defaults.keys():
|
174
176
|
raise KeyError(f"Key {key} not a valid field of {self.__class__.__name__}")
|
175
177
|
self._items[key] = value
|
176
178
|
|
177
|
-
def __delitem__(self, key):
|
179
|
+
def __delitem__(self, key: str) -> None:
|
178
180
|
del self._items[key]
|
179
181
|
|
180
|
-
def get(self, key, default=None):
|
182
|
+
def get(self, key: str, default: str = None) -> Union[str, None]: # type: ignore[override]
|
181
183
|
"""Returns the value of a setting of the My-Setting file.
|
182
184
|
|
183
185
|
If the key is not found in the My-Setting data, but it is present in the
|
@@ -202,7 +204,7 @@ class SettingsFile(MutableMapping):
|
|
202
204
|
except KeyError:
|
203
205
|
return default
|
204
206
|
|
205
|
-
def set(self, key, value):
|
207
|
+
def set(self, key: str, value: str) -> None:
|
206
208
|
"""Sets the value of a setting of the My-Setting file.
|
207
209
|
|
208
210
|
Parameters
|
@@ -214,7 +216,7 @@ class SettingsFile(MutableMapping):
|
|
214
216
|
"""
|
215
217
|
self.__setitem__(key, value)
|
216
218
|
|
217
|
-
def build(self):
|
219
|
+
def build(self) -> bytes:
|
218
220
|
"""Constructs the binary data for saving the My-Setting file.
|
219
221
|
|
220
222
|
Returns
|
@@ -227,7 +229,7 @@ class SettingsFile(MutableMapping):
|
|
227
229
|
items.update(self._items)
|
228
230
|
|
229
231
|
# Create file data
|
230
|
-
file_items = {"data": items, "checksum": 0}
|
232
|
+
file_items: Dict[str, Any] = {"data": items, "checksum": 0}
|
231
233
|
if self.version:
|
232
234
|
file_items["version"] = self.version
|
233
235
|
|
@@ -239,9 +241,10 @@ class SettingsFile(MutableMapping):
|
|
239
241
|
file_items["checksum"] = checksum
|
240
242
|
|
241
243
|
# Write data with updated checksum
|
242
|
-
|
244
|
+
bytedata: bytes = self.struct.build(file_items)
|
245
|
+
return bytedata
|
243
246
|
|
244
|
-
def save(self, path):
|
247
|
+
def save(self, path: Union[str, Path]) -> None:
|
245
248
|
"""Save the contents of the My-Setting file object.
|
246
249
|
|
247
250
|
Parameters
|
@@ -257,7 +260,7 @@ class SettingsFile(MutableMapping):
|
|
257
260
|
with open(path, "wb") as fh:
|
258
261
|
fh.write(data)
|
259
262
|
|
260
|
-
def __repr__(self):
|
263
|
+
def __repr__(self) -> str:
|
261
264
|
return f"{self.__class__.__name__}()"
|
262
265
|
|
263
266
|
|
@@ -363,7 +366,7 @@ class DevSettingFile(SettingsFile):
|
|
363
366
|
defaults = dict(entries="")
|
364
367
|
|
365
368
|
|
366
|
-
FILES = {
|
369
|
+
FILES: Dict[str, Type[SettingsFile]] = {
|
367
370
|
"DEVSETTING.DAT": DevSettingFile,
|
368
371
|
"DJMMYSETTING.DAT": DjmMySettingFile,
|
369
372
|
"MYSETTING.DAT": MySettingFile,
|