pyrekordbox 0.3.2__py3-none-any.whl → 0.4.1__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 +8 -8
- pyrekordbox/__main__.py +3 -2
- pyrekordbox/_version.py +9 -4
- pyrekordbox/anlz/__init__.py +3 -2
- pyrekordbox/anlz/file.py +4 -2
- pyrekordbox/anlz/tags.py +8 -16
- pyrekordbox/config.py +116 -47
- pyrekordbox/db6/__init__.py +2 -2
- pyrekordbox/db6/aux_files.py +3 -2
- pyrekordbox/db6/database.py +130 -177
- pyrekordbox/db6/registry.py +1 -0
- pyrekordbox/db6/smartlist.py +9 -11
- pyrekordbox/db6/tables.py +112 -132
- pyrekordbox/logger.py +0 -1
- pyrekordbox/mysettings/__init__.py +5 -4
- pyrekordbox/mysettings/file.py +3 -1
- pyrekordbox/rbxml.py +8 -12
- pyrekordbox/utils.py +4 -3
- {pyrekordbox-0.3.2.dist-info → pyrekordbox-0.4.1.dist-info}/METADATA +35 -48
- pyrekordbox-0.4.1.dist-info/RECORD +25 -0
- {pyrekordbox-0.3.2.dist-info → pyrekordbox-0.4.1.dist-info}/WHEEL +1 -1
- {pyrekordbox-0.3.2.dist-info → pyrekordbox-0.4.1.dist-info}/top_level.txt +0 -2
- docs/Makefile +0 -20
- docs/make.bat +0 -35
- docs/source/_static/images/anlz_beat.svg +0 -53
- docs/source/_static/images/anlz_file.svg +0 -204
- docs/source/_static/images/anlz_pco2.svg +0 -138
- docs/source/_static/images/anlz_pcob.svg +0 -148
- docs/source/_static/images/anlz_pcp2.svg +0 -398
- docs/source/_static/images/anlz_pcpt.svg +0 -263
- docs/source/_static/images/anlz_ppth.svg +0 -123
- docs/source/_static/images/anlz_pqt2.svg +0 -324
- docs/source/_static/images/anlz_pqt2_2.svg +0 -253
- docs/source/_static/images/anlz_pqtz.svg +0 -140
- docs/source/_static/images/anlz_pssi.svg +0 -192
- docs/source/_static/images/anlz_pssi_entry.svg +0 -191
- docs/source/_static/images/anlz_pvbr.svg +0 -125
- docs/source/_static/images/anlz_pwav.svg +0 -130
- docs/source/_static/images/anlz_pwv3.svg +0 -139
- docs/source/_static/images/anlz_pwv4.svg +0 -139
- docs/source/_static/images/anlz_pwv5.svg +0 -139
- docs/source/_static/images/anlz_pwv5_entry.svg +0 -100
- docs/source/_static/images/anlz_pwv6.svg +0 -130
- docs/source/_static/images/anlz_pwv7.svg +0 -139
- docs/source/_static/images/anlz_pwvc.svg +0 -125
- docs/source/_static/images/anlz_tag.svg +0 -110
- docs/source/_static/images/x64dbg_rb_key.png +0 -0
- docs/source/_static/logos/dark/logo_primary.svg +0 -75
- docs/source/_static/logos/light/logo_primary.svg +0 -75
- docs/source/_static/logos/mid/logo_primary.svg +0 -75
- docs/source/_templates/apidoc/module.rst_t +0 -8
- docs/source/_templates/apidoc/package.rst_t +0 -57
- docs/source/_templates/apidoc/toc.rst_t +0 -7
- docs/source/_templates/autosummary/class.rst +0 -32
- docs/source/_templates/autosummary/module.rst +0 -55
- docs/source/api.md +0 -18
- docs/source/conf.py +0 -178
- docs/source/development/changes.md +0 -3
- docs/source/development/contributing.md +0 -3
- docs/source/formats/anlz.md +0 -634
- docs/source/formats/db6.md +0 -1233
- docs/source/formats/mysetting.md +0 -392
- docs/source/formats/xml.md +0 -376
- docs/source/index.md +0 -103
- docs/source/installation.md +0 -271
- docs/source/key.md +0 -103
- docs/source/quickstart.md +0 -189
- docs/source/requirements.txt +0 -7
- docs/source/tutorial/anlz.md +0 -7
- docs/source/tutorial/configuration.md +0 -66
- docs/source/tutorial/db6.md +0 -178
- docs/source/tutorial/index.md +0 -20
- docs/source/tutorial/mysetting.md +0 -124
- docs/source/tutorial/xml.md +0 -140
- pyrekordbox/xml.py +0 -8
- pyrekordbox-0.3.2.dist-info/RECORD +0 -84
- tests/__init__.py +0 -3
- tests/test_anlz.py +0 -206
- tests/test_config.py +0 -175
- tests/test_db6.py +0 -1193
- tests/test_mysetting.py +0 -203
- tests/test_xml.py +0 -629
- {pyrekordbox-0.3.2.dist-info → pyrekordbox-0.4.1.dist-info/licenses}/LICENSE +0 -0
pyrekordbox/__init__.py
CHANGED
@@ -2,19 +2,19 @@
|
|
2
2
|
# Author: Dylan Jones
|
3
3
|
# Date: 2022-04-10
|
4
4
|
|
5
|
+
from .anlz import AnlzFile, get_anlz_paths, read_anlz_files, walk_anlz_paths
|
6
|
+
from .config import get_config, show_config, update_config
|
7
|
+
from .db6 import Rekordbox6Database
|
5
8
|
from .logger import logger
|
6
|
-
from .config import show_config, get_config, update_config
|
7
|
-
from .rbxml import RekordboxXml, XmlDuplicateError, XmlAttributeKeyError
|
8
|
-
from .anlz import get_anlz_paths, walk_anlz_paths, read_anlz_files, AnlzFile
|
9
9
|
from .mysettings import (
|
10
|
+
DevSettingFile,
|
11
|
+
DjmMySettingFile,
|
12
|
+
MySetting2File,
|
13
|
+
MySettingFile,
|
10
14
|
get_mysetting_paths,
|
11
15
|
read_mysetting_file,
|
12
|
-
MySettingFile,
|
13
|
-
MySetting2File,
|
14
|
-
DjmMySettingFile,
|
15
|
-
DevSettingFile,
|
16
16
|
)
|
17
|
-
from .
|
17
|
+
from .rbxml import RekordboxXml, XmlAttributeKeyError, XmlDuplicateError
|
18
18
|
|
19
19
|
try:
|
20
20
|
from ._version import version as __version__
|
pyrekordbox/__main__.py
CHANGED
@@ -4,11 +4,12 @@
|
|
4
4
|
|
5
5
|
import os
|
6
6
|
import re
|
7
|
-
import sys
|
8
7
|
import shutil
|
8
|
+
import sys
|
9
9
|
import urllib.request
|
10
10
|
from pathlib import Path
|
11
|
-
|
11
|
+
|
12
|
+
from pyrekordbox.config import _cache_file, write_db6_key_cache
|
12
13
|
|
13
14
|
KEY_SOURCES = [
|
14
15
|
{
|
pyrekordbox/_version.py
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
# file generated by
|
1
|
+
# file generated by setuptools-scm
|
2
2
|
# don't change, don't track in version control
|
3
|
+
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
5
|
+
|
3
6
|
TYPE_CHECKING = False
|
4
7
|
if TYPE_CHECKING:
|
5
|
-
from typing import Tuple
|
8
|
+
from typing import Tuple
|
9
|
+
from typing import Union
|
10
|
+
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
7
12
|
else:
|
8
13
|
VERSION_TUPLE = object
|
@@ -12,5 +17,5 @@ __version__: str
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
13
18
|
version_tuple: VERSION_TUPLE
|
14
19
|
|
15
|
-
__version__ = version = '0.
|
16
|
-
__version_tuple__ = version_tuple = (0,
|
20
|
+
__version__ = version = '0.4.1'
|
21
|
+
__version_tuple__ = version_tuple = (0, 4, 1)
|
pyrekordbox/anlz/__init__.py
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
import re
|
6
6
|
from pathlib import Path
|
7
7
|
from typing import Union
|
8
|
+
|
8
9
|
from . import structs
|
9
10
|
from .file import AnlzFile
|
10
11
|
|
@@ -77,7 +78,7 @@ def walk_anlz_dirs(root_dir: Union[str, Path]):
|
|
77
78
|
The path of the root directory.
|
78
79
|
|
79
80
|
Yields
|
80
|
-
|
81
|
+
------
|
81
82
|
anlz_dir : str
|
82
83
|
The path of a directory containing ANLZ files
|
83
84
|
"""
|
@@ -96,7 +97,7 @@ def walk_anlz_paths(root_dir: Union[str, Path]):
|
|
96
97
|
The path of the root directory.
|
97
98
|
|
98
99
|
Yields
|
99
|
-
|
100
|
+
------
|
100
101
|
anlz_dir : str
|
101
102
|
The path of a directory containing ANLZ files.
|
102
103
|
anlz_files : Sequence of str
|
pyrekordbox/anlz/file.py
CHANGED
@@ -6,10 +6,12 @@ import logging
|
|
6
6
|
from collections import abc
|
7
7
|
from pathlib import Path
|
8
8
|
from typing import Union
|
9
|
-
|
10
|
-
from . import structs
|
9
|
+
|
11
10
|
from construct import Int16ub
|
12
11
|
|
12
|
+
from . import structs
|
13
|
+
from .tags import TAGS
|
14
|
+
|
13
15
|
logger = logging.getLogger(__name__)
|
14
16
|
|
15
17
|
XOR_MASK = bytearray.fromhex("CB E1 EE FA E5 EE AD EE E9 D2 E9 EB E1 E9 F3 E8 E9 F4 E1")
|
pyrekordbox/anlz/tags.py
CHANGED
@@ -4,7 +4,9 @@
|
|
4
4
|
|
5
5
|
import logging
|
6
6
|
from abc import ABC
|
7
|
+
|
7
8
|
import numpy as np
|
9
|
+
|
8
10
|
from . import structs
|
9
11
|
|
10
12
|
logger = logging.getLogger(__name__)
|
@@ -160,13 +162,9 @@ class PQTZAnlzTag(AbstractAnlzTag):
|
|
160
162
|
n_bpms = len(bpms)
|
161
163
|
n_times = len(times)
|
162
164
|
if n_bpms != n_beats:
|
163
|
-
raise ValueError(
|
164
|
-
f"Number of bpms not equal to number of beats: {n_bpms} != {n_beats}"
|
165
|
-
)
|
165
|
+
raise ValueError(f"Number of bpms not equal to number of beats: {n_bpms} != {n_beats}")
|
166
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
|
-
)
|
167
|
+
raise ValueError(f"Number of times not equal to number of beats: {n_bpms} != {n_times}")
|
170
168
|
|
171
169
|
# For now only values of existing beats can be set
|
172
170
|
if n_beats != n:
|
@@ -182,9 +180,7 @@ class PQTZAnlzTag(AbstractAnlzTag):
|
|
182
180
|
n = len(self.content.entries)
|
183
181
|
n_new = len(beats)
|
184
182
|
if n_new != n:
|
185
|
-
raise ValueError(
|
186
|
-
f"Number of beats not equal to current content length: {n_new} != {n}"
|
187
|
-
)
|
183
|
+
raise ValueError(f"Number of beats not equal to current content length: {n_new} != {n}")
|
188
184
|
|
189
185
|
for i, beat in enumerate(beats):
|
190
186
|
self.content.entries[i].beat = beat
|
@@ -193,9 +189,7 @@ class PQTZAnlzTag(AbstractAnlzTag):
|
|
193
189
|
n = len(self.content.entries)
|
194
190
|
n_new = len(bpms)
|
195
191
|
if n_new != n:
|
196
|
-
raise ValueError(
|
197
|
-
f"Number of bpms not equal to current content length: {n_new} != {n}"
|
198
|
-
)
|
192
|
+
raise ValueError(f"Number of bpms not equal to current content length: {n_new} != {n}")
|
199
193
|
|
200
194
|
for i, bpm in enumerate(bpms):
|
201
195
|
self.content.entries[i].tempo = int(bpm * 100)
|
@@ -204,9 +198,7 @@ class PQTZAnlzTag(AbstractAnlzTag):
|
|
204
198
|
n = len(self.content.entries)
|
205
199
|
n_new = len(times)
|
206
200
|
if n_new != n:
|
207
|
-
raise ValueError(
|
208
|
-
f"Number of times not equal to current content length: {n_new} != {n}"
|
209
|
-
)
|
201
|
+
raise ValueError(f"Number of times not equal to current content length: {n_new} != {n}")
|
210
202
|
|
211
203
|
for i, t in enumerate(times):
|
212
204
|
self.content.entries[i].time = int(1000 * t)
|
@@ -439,7 +431,7 @@ class PWV5AnlzTag(AbstractAnlzTag):
|
|
439
431
|
LEN_HEADER = 24
|
440
432
|
|
441
433
|
def get(self):
|
442
|
-
"""Parse the Waveform Color Detail Tag (PWV5)
|
434
|
+
"""Parse the Waveform Color Detail Tag (PWV5).
|
443
435
|
|
444
436
|
The format of the entries is:
|
445
437
|
|
pyrekordbox/config.py
CHANGED
@@ -30,7 +30,7 @@ logger = logging.getLogger(__name__)
|
|
30
30
|
|
31
31
|
# Cache file for pyrekordbox data
|
32
32
|
_cache_file_version = 2
|
33
|
-
|
33
|
+
_cache_file_name = "rb.cache"
|
34
34
|
|
35
35
|
# Define empty pyrekordbox configuration
|
36
36
|
__config__ = {
|
@@ -40,6 +40,7 @@ __config__ = {
|
|
40
40
|
},
|
41
41
|
"rekordbox5": {},
|
42
42
|
"rekordbox6": {},
|
43
|
+
"rekordbox7": {},
|
43
44
|
}
|
44
45
|
|
45
46
|
|
@@ -47,10 +48,30 @@ class InvalidApplicationDirname(Exception):
|
|
47
48
|
pass
|
48
49
|
|
49
50
|
|
51
|
+
def get_appdata_dir() -> Path:
|
52
|
+
"""Returns the path of the application data directory.
|
53
|
+
|
54
|
+
On Windows, the application data is stored in `/Users/user/AppData/Roaming`.
|
55
|
+
On macOS the application data is stored in `~/Libary/Application Support`.
|
56
|
+
"""
|
57
|
+
if sys.platform == "win32":
|
58
|
+
# Windows: located in /Users/user/AppData/Roaming/
|
59
|
+
app_data = Path(os.environ["AppData"])
|
60
|
+
elif sys.platform == "darwin":
|
61
|
+
# MacOS: located in ~/Library/Application Support/
|
62
|
+
app_data = Path("~").expanduser() / "Library" / "Application Support"
|
63
|
+
else:
|
64
|
+
# Linux: not supported
|
65
|
+
logger.warning(f"OS {sys.platform} not supported!")
|
66
|
+
return Path("~").expanduser() / ".local" / "share"
|
67
|
+
return app_data
|
68
|
+
|
69
|
+
|
50
70
|
def get_pioneer_install_dir(path: Union[str, Path] = None) -> Path: # pragma: no cover
|
51
71
|
"""Returns the path of the Pioneer program installation directory.
|
52
72
|
|
53
|
-
On Windows, the Pioneer program data is stored in `/ProgramFiles/Pioneer
|
73
|
+
On Windows, the Pioneer program data is stored in `/ProgramFiles/Pioneer`.
|
74
|
+
For rekordbox version 7 this has changed to `/ProgramFiles/rekordbox`.
|
54
75
|
On macOS the program data is somewhere in `/Applications/`.
|
55
76
|
|
56
77
|
Parameters
|
@@ -68,7 +89,7 @@ def get_pioneer_install_dir(path: Union[str, Path] = None) -> Path: # pragma: n
|
|
68
89
|
if sys.platform == "win32":
|
69
90
|
# Windows: located in /ProgramFiles/Pioneer
|
70
91
|
program_files = os.environ["ProgramFiles"].replace("(x86)", "").strip()
|
71
|
-
path = Path(program_files)
|
92
|
+
path = Path(program_files)
|
72
93
|
elif sys.platform == "darwin":
|
73
94
|
# MacOS: located in /Applications/
|
74
95
|
path = Path("/Applications")
|
@@ -232,12 +253,7 @@ def read_rekordbox6_asar(rb6_install_dir: Union[str, Path]) -> str:
|
|
232
253
|
if not str(rb6_install_dir).endswith(".app"):
|
233
254
|
rb6_install_dir = rb6_install_dir / "rekordbox.app"
|
234
255
|
location = (
|
235
|
-
rb6_install_dir
|
236
|
-
/ "Contents"
|
237
|
-
/ "MacOS"
|
238
|
-
/ "rekordboxAgent.app"
|
239
|
-
/ "Contents"
|
240
|
-
/ "Resources"
|
256
|
+
rb6_install_dir / "Contents" / "MacOS" / "rekordboxAgent.app" / "Contents" / "Resources"
|
241
257
|
)
|
242
258
|
encoding = "cp437"
|
243
259
|
else:
|
@@ -247,7 +263,7 @@ def read_rekordbox6_asar(rb6_install_dir: Union[str, Path]) -> str:
|
|
247
263
|
# Read asar file
|
248
264
|
path = (location / "app.asar").absolute()
|
249
265
|
with open(path, "rb") as fh:
|
250
|
-
data = fh.read().decode(encoding)
|
266
|
+
data = fh.read().decode(encoding, errors="replace")
|
251
267
|
return data
|
252
268
|
|
253
269
|
|
@@ -284,6 +300,12 @@ def _get_rb_config(
|
|
284
300
|
config : dict
|
285
301
|
The program configuration.
|
286
302
|
"""
|
303
|
+
if sys.platform == "win32":
|
304
|
+
if major_version >= 7:
|
305
|
+
pioneer_install_dir = pioneer_install_dir / "rekordbox"
|
306
|
+
else:
|
307
|
+
pioneer_install_dir = pioneer_install_dir / "Pioneer"
|
308
|
+
|
287
309
|
if application_dirname:
|
288
310
|
# Applitcation dirname is given, only extract version from it
|
289
311
|
# `major_version` is compared to the version string
|
@@ -328,7 +350,7 @@ def _get_rb_config(
|
|
328
350
|
logger.debug("Found Rekordbox %s install-dir: '%s'", major_version, rb_prog_dir)
|
329
351
|
|
330
352
|
# Get Rekordbox application directory path for major release `major_version`
|
331
|
-
name = "rekordbox6" if major_version
|
353
|
+
name = "rekordbox6" if major_version >= 6 else "rekordbox"
|
332
354
|
rb_app_dir = pioneer_app_dir / name
|
333
355
|
if not rb_app_dir.exists():
|
334
356
|
raise FileNotFoundError(f"The directory '{rb_app_dir}' doesn't exist!")
|
@@ -337,7 +359,7 @@ def _get_rb_config(
|
|
337
359
|
# Get Rekordbox database locations for major release `major_version`
|
338
360
|
settings = read_rekordbox_settings(rb_app_dir)
|
339
361
|
db_dir = Path(settings["masterDbDirectory"])
|
340
|
-
db_filename = "master.db" if major_version
|
362
|
+
db_filename = "master.db" if major_version >= 6 else "datafile.edb"
|
341
363
|
db_path = db_dir / db_filename
|
342
364
|
if not db_path.exists():
|
343
365
|
raise FileNotFoundError(f"The Rekordbox database '{db_path}' doesn't exist!")
|
@@ -352,9 +374,7 @@ def _get_rb_config(
|
|
352
374
|
return conf
|
353
375
|
|
354
376
|
|
355
|
-
def _get_rb5_config(
|
356
|
-
pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str = ""
|
357
|
-
) -> dict:
|
377
|
+
def _get_rb5_config(pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str = "") -> dict:
|
358
378
|
"""Get the program configuration for Rekordbox v5.x.x."""
|
359
379
|
major_version = 5
|
360
380
|
conf = _get_rb_config(pioneer_prog_dir, pioneer_app_dir, major_version, dirname)
|
@@ -363,7 +383,6 @@ def _get_rb5_config(
|
|
363
383
|
|
364
384
|
def _extract_pw(pioneer_install_dir: Path) -> str: # pragma: no cover
|
365
385
|
"""Extract the password for decrypting the Rekordbox 6 database key."""
|
366
|
-
|
367
386
|
asar_data = read_rekordbox6_asar(pioneer_install_dir)
|
368
387
|
match_result = re.search('pass: ".(.*?)"', asar_data)
|
369
388
|
if match_result is None:
|
@@ -407,8 +426,7 @@ class KeyExtractor:
|
|
407
426
|
pid = get_rekordbox_pid()
|
408
427
|
if pid:
|
409
428
|
raise RuntimeError(
|
410
|
-
"Rekordbox is running. "
|
411
|
-
"Please close Rekordbox before running the `KeyExtractor`."
|
429
|
+
"Rekordbox is running. Please close Rekordbox before running the `KeyExtractor`."
|
412
430
|
)
|
413
431
|
# Spawn Rekordbox process and attach to it
|
414
432
|
pid = frida.spawn(self.executable)
|
@@ -427,12 +445,16 @@ class KeyExtractor:
|
|
427
445
|
|
428
446
|
|
429
447
|
def write_db6_key_cache(key: str) -> None: # pragma: no cover
|
430
|
-
"""Writes the decrypted Rekordbox6 database key to the cache file.
|
448
|
+
r"""Writes the decrypted Rekordbox6 database key to the cache file.
|
431
449
|
|
432
450
|
This method can also be used to manually cache the database key, provided
|
433
451
|
the user has found the key somewhere else. The key can be, for example,
|
434
452
|
found in some other projects that hard-coded it.
|
435
453
|
|
454
|
+
The cache file is stored in the application data directory of pyrekordbox:
|
455
|
+
Windows: `C:\Users\<user>\AppData\Roaming\pyrekordbox`
|
456
|
+
macOS: `~/Library/Application Support/pyrekordbox`
|
457
|
+
|
436
458
|
Parameters
|
437
459
|
----------
|
438
460
|
key : str
|
@@ -454,32 +476,30 @@ def write_db6_key_cache(key: str) -> None: # pragma: no cover
|
|
454
476
|
lines.append(f"version: {_cache_file_version}")
|
455
477
|
lines.append("dp: " + key)
|
456
478
|
text = "\n".join(lines)
|
457
|
-
with open(_cache_file, "w") as fh:
|
458
|
-
fh.write(text)
|
459
|
-
# Set the config key to make sure the key is present after calling method
|
460
|
-
__config__["rekordbox6"]["dp"] = key
|
461
479
|
|
480
|
+
cache_file = get_appdata_dir() / "pyrekordbox" / _cache_file_name
|
481
|
+
if not cache_file.parent.exists():
|
482
|
+
cache_file.parent.mkdir()
|
462
483
|
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
484
|
+
with open(cache_file, "w") as fh:
|
485
|
+
fh.write(text)
|
486
|
+
# Set the config key to make sure the key is present after calling method
|
487
|
+
if __config__["rekordbox6"]:
|
488
|
+
__config__["rekordbox6"]["dp"] = key
|
489
|
+
if __config__["rekordbox7"]:
|
490
|
+
__config__["rekordbox7"]["dp"] = key
|
469
491
|
|
470
|
-
# Read Rekordbox 6 'options.json' and check db_path
|
471
|
-
opts = read_rekordbox6_options(pioneer_app_dir)
|
472
|
-
db_path = Path(opts["db-path"])
|
473
|
-
db_dir = db_path.parent
|
474
|
-
assert str(conf["db_dir"]) == str(db_dir)
|
475
|
-
assert str(conf["db_path"]) == str(db_path)
|
476
492
|
|
493
|
+
def _update_sqlite_key(opts, conf):
|
477
494
|
cache_version = 0
|
478
495
|
pw, dp = "", ""
|
479
|
-
|
480
|
-
|
496
|
+
|
497
|
+
cache_file = get_appdata_dir() / "pyrekordbox" / _cache_file_name
|
498
|
+
|
499
|
+
if cache_file.exists(): # pragma: no cover
|
500
|
+
logger.debug("Found cache file %s", cache_file)
|
481
501
|
# Read cache file
|
482
|
-
with open(
|
502
|
+
with open(cache_file, "r") as fh:
|
483
503
|
text = fh.read()
|
484
504
|
lines = text.splitlines()
|
485
505
|
if lines[0].startswith("version:"):
|
@@ -520,9 +540,10 @@ def _get_rb6_config(
|
|
520
540
|
if sys.platform == "win32":
|
521
541
|
executable = conf["install_dir"] / "rekordbox.exe"
|
522
542
|
elif sys.platform == "darwin":
|
523
|
-
|
524
|
-
|
525
|
-
|
543
|
+
install_dir = conf["install_dir"]
|
544
|
+
if not str(install_dir).endswith(".app"):
|
545
|
+
install_dir = install_dir / "rekordbox.app"
|
546
|
+
executable = install_dir / "Contents" / "MacOS" / "rekordbox"
|
526
547
|
else:
|
527
548
|
# Linux: not supported
|
528
549
|
logger.warning(f"OS {sys.platform} not supported!")
|
@@ -552,6 +573,40 @@ def _get_rb6_config(
|
|
552
573
|
if dp:
|
553
574
|
conf["dp"] = dp
|
554
575
|
|
576
|
+
|
577
|
+
def _get_rb6_config(pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str = "") -> dict:
|
578
|
+
"""Get the program configuration for Rekordbox v6.x.x."""
|
579
|
+
major_version = 6
|
580
|
+
conf = _get_rb_config(pioneer_prog_dir, pioneer_app_dir, major_version, dirname)
|
581
|
+
|
582
|
+
# Read Rekordbox 6 'options.json' and check db_path
|
583
|
+
opts = read_rekordbox6_options(pioneer_app_dir)
|
584
|
+
db_path = Path(opts["db-path"])
|
585
|
+
db_dir = db_path.parent
|
586
|
+
assert str(conf["db_dir"]) == str(db_dir)
|
587
|
+
assert str(conf["db_path"]) == str(db_path)
|
588
|
+
|
589
|
+
# Update SQLite key
|
590
|
+
_update_sqlite_key(opts, conf)
|
591
|
+
|
592
|
+
return conf
|
593
|
+
|
594
|
+
|
595
|
+
def _get_rb7_config(pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str = "") -> dict:
|
596
|
+
"""Get the program configuration for Rekordbox v7.x.x."""
|
597
|
+
major_version = 7
|
598
|
+
conf = _get_rb_config(pioneer_prog_dir, pioneer_app_dir, major_version, dirname)
|
599
|
+
|
600
|
+
# Read Rekordbox 6 'options.json' and check db_path
|
601
|
+
opts = read_rekordbox6_options(pioneer_app_dir)
|
602
|
+
db_path = Path(opts["db-path"])
|
603
|
+
db_dir = db_path.parent
|
604
|
+
assert str(conf["db_dir"]) == str(db_dir)
|
605
|
+
assert str(conf["db_path"]) == str(db_path)
|
606
|
+
|
607
|
+
# Update SQLite key
|
608
|
+
_update_sqlite_key(opts, conf)
|
609
|
+
|
555
610
|
return conf
|
556
611
|
|
557
612
|
|
@@ -617,6 +672,7 @@ def update_config(
|
|
617
672
|
pioneer_app_dir: Union[str, Path] = None,
|
618
673
|
rb5_install_dirname: str = "",
|
619
674
|
rb6_install_dirname: str = "",
|
675
|
+
rb7_install_dirname: str = "",
|
620
676
|
):
|
621
677
|
"""Update the pyrekordbox configuration.
|
622
678
|
|
@@ -643,6 +699,9 @@ def update_config(
|
|
643
699
|
rb6_install_dirname : str, optional
|
644
700
|
The name of the Rekordbox 6 installation directory. By default, the normal
|
645
701
|
directory name is used (Windows: 'rekordbox 6.x.x', macOS: 'rekordbox 6.app').
|
702
|
+
rb7_install_dirname : str, optional
|
703
|
+
The name of the Rekordbox 7 installation directory. By default, the normal
|
704
|
+
directory name is used (Windows: 'rekordbox 7.x.x', macOS: 'rekordbox 7.app').
|
646
705
|
"""
|
647
706
|
# Read config file
|
648
707
|
conf = read_pyrekordbox_configuration()
|
@@ -654,6 +713,8 @@ def update_config(
|
|
654
713
|
rb5_install_dirname = conf["rekordbox5-install-dirname"]
|
655
714
|
if not rb6_install_dirname and "rekordbox6-install-dirname" in conf:
|
656
715
|
rb6_install_dirname = conf["rekordbox6-install-dirname"]
|
716
|
+
if not rb7_install_dirname and "rekordbox7-install-dirname" in conf:
|
717
|
+
rb7_install_dirname = conf["rekordbox7-install-dirname"]
|
657
718
|
|
658
719
|
# Pioneer installation directory
|
659
720
|
try:
|
@@ -673,22 +734,25 @@ def update_config(
|
|
673
734
|
|
674
735
|
# Update Rekordbox 5 config
|
675
736
|
try:
|
676
|
-
conf = _get_rb5_config(
|
677
|
-
pioneer_install_dir, pioneer_app_dir, rb5_install_dirname
|
678
|
-
)
|
737
|
+
conf = _get_rb5_config(pioneer_install_dir, pioneer_app_dir, rb5_install_dirname)
|
679
738
|
__config__["rekordbox5"].update(conf)
|
680
739
|
except FileNotFoundError as e:
|
681
740
|
logger.info(e)
|
682
741
|
|
683
742
|
# Update Rekordbox 6 config
|
684
743
|
try:
|
685
|
-
conf = _get_rb6_config(
|
686
|
-
pioneer_install_dir, pioneer_app_dir, rb6_install_dirname
|
687
|
-
)
|
744
|
+
conf = _get_rb6_config(pioneer_install_dir, pioneer_app_dir, rb6_install_dirname)
|
688
745
|
__config__["rekordbox6"].update(conf)
|
689
746
|
except FileNotFoundError as e:
|
690
747
|
logger.info(e)
|
691
748
|
|
749
|
+
# Update Rekordbox 7 config
|
750
|
+
try:
|
751
|
+
conf = _get_rb7_config(pioneer_install_dir, pioneer_app_dir, rb7_install_dirname)
|
752
|
+
__config__["rekordbox7"].update(conf)
|
753
|
+
except FileNotFoundError as e:
|
754
|
+
logger.info(e)
|
755
|
+
|
692
756
|
|
693
757
|
def get_config(section: str, key: str = None):
|
694
758
|
"""Gets a section or value of the pyrekordbox configuration.
|
@@ -721,6 +785,7 @@ def pformat_config(indent: str = " ", hw: int = 14, delim: str = " = ") -> str
|
|
721
785
|
pioneer = get_config("pioneer")
|
722
786
|
rb5 = get_config("rekordbox5")
|
723
787
|
rb6 = get_config("rekordbox6")
|
788
|
+
rb7 = get_config("rekordbox7")
|
724
789
|
|
725
790
|
lines = ["Pioneer:"]
|
726
791
|
lines += [f"{indent}{k + delim:<{hw}} {pioneer[k]}" for k in sorted(pioneer.keys())]
|
@@ -731,6 +796,10 @@ def pformat_config(indent: str = " ", hw: int = 14, delim: str = " = ") -> str
|
|
731
796
|
if rb6:
|
732
797
|
rb6_keys = [k for k in rb6.keys() if k not in ("dp", "p")]
|
733
798
|
lines += [f"{indent}{k + delim:<{hw}} {rb6[k]}" for k in sorted(rb6_keys)]
|
799
|
+
lines.append("Rekordbox 7:")
|
800
|
+
if rb7:
|
801
|
+
rb7_keys = [k for k in rb7.keys() if k not in ("dp", "p")]
|
802
|
+
lines += [f"{indent}{k + delim:<{hw}} {rb7[k]}" for k in sorted(rb7_keys)]
|
734
803
|
return "\n".join(lines)
|
735
804
|
|
736
805
|
|
pyrekordbox/db6/__init__.py
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
# Author: Dylan Jones
|
3
3
|
# Date: 2022-05-07
|
4
4
|
|
5
|
+
from .database import Rekordbox6Database
|
6
|
+
from .smartlist import SmartList
|
5
7
|
from .tables import (
|
6
8
|
AgentRegistry,
|
7
9
|
CloudAgentRegistry,
|
@@ -41,5 +43,3 @@ from .tables import (
|
|
41
43
|
SettingFile,
|
42
44
|
UuidIDMap,
|
43
45
|
)
|
44
|
-
from .smartlist import SmartList
|
45
|
-
from .database import Rekordbox6Database, open_rekordbox_database
|
pyrekordbox/db6/aux_files.py
CHANGED
@@ -2,9 +2,10 @@
|
|
2
2
|
# Author: Dylan Jones
|
3
3
|
# Date: 2023-09-10
|
4
4
|
|
5
|
-
from pathlib import Path
|
6
|
-
from datetime import datetime
|
7
5
|
import xml.etree.cElementTree as xml
|
6
|
+
from datetime import datetime
|
7
|
+
from pathlib import Path
|
8
|
+
|
8
9
|
from ..config import get_config
|
9
10
|
from ..utils import pretty_xml
|
10
11
|
|