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.
- docs/source/formats/anlz.md +178 -7
- docs/source/formats/db6.md +1 -1
- docs/source/index.md +2 -6
- docs/source/quickstart.md +68 -45
- docs/source/tutorial/index.md +1 -1
- pyrekordbox/__init__.py +1 -1
- pyrekordbox/_version.py +2 -2
- pyrekordbox/anlz/file.py +39 -0
- pyrekordbox/anlz/structs.py +3 -5
- pyrekordbox/config.py +71 -27
- pyrekordbox/db6/database.py +260 -33
- pyrekordbox/db6/registry.py +22 -0
- pyrekordbox/db6/tables.py +3 -4
- {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/METADATA +12 -11
- pyrekordbox-0.2.2.dist-info/RECORD +80 -0
- {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/top_level.txt +0 -2
- tests/test_config.py +175 -0
- tests/test_db6.py +78 -0
- build/lib/build/lib/docs/source/conf.py +0 -178
- build/lib/build/lib/pyrekordbox/__init__.py +0 -22
- build/lib/build/lib/pyrekordbox/__main__.py +0 -204
- build/lib/build/lib/pyrekordbox/_version.py +0 -16
- build/lib/build/lib/pyrekordbox/anlz/__init__.py +0 -127
- build/lib/build/lib/pyrekordbox/anlz/file.py +0 -186
- build/lib/build/lib/pyrekordbox/anlz/structs.py +0 -299
- build/lib/build/lib/pyrekordbox/anlz/tags.py +0 -508
- build/lib/build/lib/pyrekordbox/config.py +0 -596
- build/lib/build/lib/pyrekordbox/db6/__init__.py +0 -45
- build/lib/build/lib/pyrekordbox/db6/aux_files.py +0 -213
- build/lib/build/lib/pyrekordbox/db6/database.py +0 -1808
- build/lib/build/lib/pyrekordbox/db6/registry.py +0 -304
- build/lib/build/lib/pyrekordbox/db6/tables.py +0 -1618
- build/lib/build/lib/pyrekordbox/logger.py +0 -23
- build/lib/build/lib/pyrekordbox/mysettings/__init__.py +0 -32
- build/lib/build/lib/pyrekordbox/mysettings/file.py +0 -369
- build/lib/build/lib/pyrekordbox/mysettings/structs.py +0 -282
- build/lib/build/lib/pyrekordbox/utils.py +0 -162
- build/lib/build/lib/pyrekordbox/xml.py +0 -1294
- build/lib/build/lib/tests/__init__.py +0 -3
- build/lib/build/lib/tests/test_anlz.py +0 -206
- build/lib/build/lib/tests/test_db6.py +0 -1039
- build/lib/build/lib/tests/test_mysetting.py +0 -203
- build/lib/build/lib/tests/test_xml.py +0 -629
- build/lib/docs/source/conf.py +0 -178
- build/lib/pyrekordbox/__init__.py +0 -22
- build/lib/pyrekordbox/__main__.py +0 -204
- build/lib/pyrekordbox/_version.py +0 -16
- build/lib/pyrekordbox/anlz/__init__.py +0 -127
- build/lib/pyrekordbox/anlz/file.py +0 -186
- build/lib/pyrekordbox/anlz/structs.py +0 -299
- build/lib/pyrekordbox/anlz/tags.py +0 -508
- build/lib/pyrekordbox/config.py +0 -596
- build/lib/pyrekordbox/db6/__init__.py +0 -45
- build/lib/pyrekordbox/db6/aux_files.py +0 -213
- build/lib/pyrekordbox/db6/database.py +0 -1808
- build/lib/pyrekordbox/db6/registry.py +0 -304
- build/lib/pyrekordbox/db6/tables.py +0 -1618
- build/lib/pyrekordbox/logger.py +0 -23
- build/lib/pyrekordbox/mysettings/__init__.py +0 -32
- build/lib/pyrekordbox/mysettings/file.py +0 -369
- build/lib/pyrekordbox/mysettings/structs.py +0 -282
- build/lib/pyrekordbox/utils.py +0 -162
- build/lib/pyrekordbox/xml.py +0 -1294
- build/lib/tests/__init__.py +0 -3
- build/lib/tests/test_anlz.py +0 -206
- build/lib/tests/test_db6.py +0 -1039
- build/lib/tests/test_mysetting.py +0 -203
- build/lib/tests/test_xml.py +0 -629
- pyrekordbox-0.2.1.dist-info/RECORD +0 -129
- {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/LICENSE +0 -0
- {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/WHEEL +0 -0
build/lib/pyrekordbox/config.py
DELETED
@@ -1,596 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
# Author: Dylan Jones
|
3
|
-
# Date: 2022-04-10
|
4
|
-
|
5
|
-
"""Configuration handling for pyrekordbox.
|
6
|
-
|
7
|
-
Contains all the path and settings handling of the Rekordbox installation(s) on
|
8
|
-
the users machine.
|
9
|
-
"""
|
10
|
-
|
11
|
-
import os
|
12
|
-
import re
|
13
|
-
import sys
|
14
|
-
import logging
|
15
|
-
import base64
|
16
|
-
import blowfish
|
17
|
-
import json
|
18
|
-
import xml.etree.cElementTree as xml
|
19
|
-
from pathlib import Path
|
20
|
-
from typing import Union
|
21
|
-
|
22
|
-
logger = logging.getLogger(__name__)
|
23
|
-
|
24
|
-
# Cache file for pyrekordbox data
|
25
|
-
_cache_file_version = 2
|
26
|
-
_cache_file = Path(__file__).parent / "rb.cache"
|
27
|
-
|
28
|
-
# Define empty pyrekordbox configuration
|
29
|
-
__config__ = {
|
30
|
-
"pioneer": {
|
31
|
-
"app_dir": Path(),
|
32
|
-
"install_dir": Path(),
|
33
|
-
},
|
34
|
-
"rekordbox5": {},
|
35
|
-
"rekordbox6": {},
|
36
|
-
}
|
37
|
-
|
38
|
-
|
39
|
-
class InvalidApplicationDirname(Exception):
|
40
|
-
pass
|
41
|
-
|
42
|
-
|
43
|
-
def get_pioneer_install_dir(path: Union[str, Path] = None) -> Path: # pragma: no cover
|
44
|
-
"""Returns the path of the Pioneer program installation directory.
|
45
|
-
|
46
|
-
On Windows, the Pioneer program data is stored in `/ProgramFiles/Pioneer`
|
47
|
-
On macOS the program data is somewhere in `/Applications/`.
|
48
|
-
|
49
|
-
Parameters
|
50
|
-
----------
|
51
|
-
path : str or Path, optional
|
52
|
-
If a path is given it will only be checked for validity. Otherwise,
|
53
|
-
the default Pioneer directory will be constructed and checked.
|
54
|
-
|
55
|
-
Returns
|
56
|
-
-------
|
57
|
-
pioneer_path : Path
|
58
|
-
The path to the Pioneer program installation data.
|
59
|
-
"""
|
60
|
-
if path is None:
|
61
|
-
if sys.platform == "win32":
|
62
|
-
# Windows: located in /ProgramFiles/Pioneer
|
63
|
-
program_files = os.environ["ProgramFiles"].replace("(x86)", "").strip()
|
64
|
-
path = Path(program_files) / "Pioneer"
|
65
|
-
elif sys.platform == "darwin":
|
66
|
-
# MacOS: located in /Applications/
|
67
|
-
path = Path("/Applications")
|
68
|
-
else:
|
69
|
-
# Linux: not supported
|
70
|
-
logger.warning(f"OS {sys.platform} not supported!")
|
71
|
-
return Path()
|
72
|
-
else:
|
73
|
-
path = Path(path)
|
74
|
-
|
75
|
-
path = path.absolute()
|
76
|
-
if not path.exists():
|
77
|
-
raise FileNotFoundError(f"The Pioneer install-dir {path} does not exist!")
|
78
|
-
|
79
|
-
return path
|
80
|
-
|
81
|
-
|
82
|
-
def get_pioneer_app_dir(path: Union[str, Path] = None) -> Path: # pragma: no cover
|
83
|
-
"""Returns the path of the Pioneer application data directory.
|
84
|
-
|
85
|
-
On Windows, the Pioneer application data is stored in `/Users/user/AppData/Roaming`
|
86
|
-
On macOS the application data is somewhere in `~/Libary/Application Support`.
|
87
|
-
|
88
|
-
Parameters
|
89
|
-
----------
|
90
|
-
path : str or Path, optional
|
91
|
-
If a path is given it will only be checked for validity. Otherwise,
|
92
|
-
the default Pioneer directory will be constructed and checked.
|
93
|
-
|
94
|
-
Returns
|
95
|
-
-------
|
96
|
-
pioneer_path : Path
|
97
|
-
The path to the Pioneer application data.
|
98
|
-
"""
|
99
|
-
if path is None:
|
100
|
-
if sys.platform == "win32":
|
101
|
-
# Windows: located in /Users/user/AppData/Roaming/
|
102
|
-
app_data = Path(os.environ["AppData"])
|
103
|
-
elif sys.platform == "darwin":
|
104
|
-
# MacOS: located in ~/Library/Application Support/
|
105
|
-
app_data = Path("~").expanduser() / "Library" / "Application Support"
|
106
|
-
else:
|
107
|
-
# Linux: not supported
|
108
|
-
logger.warning(f"OS {sys.platform} not supported!")
|
109
|
-
return Path()
|
110
|
-
# Pioneer app data
|
111
|
-
path = app_data / "Pioneer"
|
112
|
-
else:
|
113
|
-
path = Path(path)
|
114
|
-
|
115
|
-
path = path.absolute()
|
116
|
-
if not path.exists():
|
117
|
-
raise FileNotFoundError(f"The Pioneer app-dir {path} does not exist!")
|
118
|
-
|
119
|
-
return path
|
120
|
-
|
121
|
-
|
122
|
-
def _convert_type(s):
|
123
|
-
# Try to parse as int, float, list of int, list of float
|
124
|
-
types_ = int, float
|
125
|
-
for type_ in types_:
|
126
|
-
try:
|
127
|
-
return type_(s)
|
128
|
-
except ValueError:
|
129
|
-
pass
|
130
|
-
try:
|
131
|
-
return [type_(x) for x in s.split(",")]
|
132
|
-
except ValueError:
|
133
|
-
pass
|
134
|
-
|
135
|
-
return s
|
136
|
-
|
137
|
-
|
138
|
-
def read_rekordbox_settings(rekordbox_app_dir: Union[str, Path]) -> dict:
|
139
|
-
"""Finds and parses the 'rekordbox3.settings' file in the Rekordbox 5 or 6 app-dir.
|
140
|
-
|
141
|
-
The settings file usually is called 'rekordbox3.settings' and is
|
142
|
-
located in the application data directory of the corresponding Rekordbox
|
143
|
-
(major) version.
|
144
|
-
|
145
|
-
Parameters
|
146
|
-
----------
|
147
|
-
rekordbox_app_dir : str or Path
|
148
|
-
The path of the application-data directory of Rekordbox 5 or 6.
|
149
|
-
|
150
|
-
Returns
|
151
|
-
-------
|
152
|
-
settings : dict
|
153
|
-
The parsed Rekordbox settings data.
|
154
|
-
"""
|
155
|
-
# Get path of the settings file
|
156
|
-
rekordbox_app_dir = Path(rekordbox_app_dir)
|
157
|
-
path = rekordbox_app_dir / "rekordbox3.settings"
|
158
|
-
|
159
|
-
# Parse the settings file
|
160
|
-
settings = dict()
|
161
|
-
tree = xml.parse(path)
|
162
|
-
for element in tree.findall("VALUE"):
|
163
|
-
name = element.attrib["name"]
|
164
|
-
try:
|
165
|
-
val = _convert_type(element.attrib["val"])
|
166
|
-
except KeyError:
|
167
|
-
device_setup = element.find("DEVICESETUP")
|
168
|
-
val = {k: _convert_type(v) for k, v in device_setup.attrib.items()}
|
169
|
-
settings[name] = val
|
170
|
-
return settings
|
171
|
-
|
172
|
-
|
173
|
-
def read_rekordbox6_options(pioneer_app_dir: Union[str, Path]) -> dict:
|
174
|
-
"""Finds and parses the Rekordbox 6 `options.json` file with additional settings.
|
175
|
-
|
176
|
-
The options file contains additional settings used by Rekordbox 6, for example the
|
177
|
-
path of the new `master.db` database. It also contains some data nedded to open
|
178
|
-
the database, which is encrypted using SQLCipher.
|
179
|
-
|
180
|
-
Parameters
|
181
|
-
----------
|
182
|
-
pioneer_app_dir : str or Path, optional
|
183
|
-
The path of the Pioneer application data.
|
184
|
-
|
185
|
-
Returns
|
186
|
-
-------
|
187
|
-
options : dict
|
188
|
-
The parsed rekordbox 6 options data.
|
189
|
-
"""
|
190
|
-
# Get path of the options file
|
191
|
-
pioneer_app_dir = Path(pioneer_app_dir)
|
192
|
-
opt_path = pioneer_app_dir / "rekordboxAgent" / "storage" / "options.json"
|
193
|
-
# Read and parse the options file
|
194
|
-
with open(opt_path, "r") as fh:
|
195
|
-
data = json.load(fh)
|
196
|
-
options = dict()
|
197
|
-
for key, value in data["options"]:
|
198
|
-
options[key] = value
|
199
|
-
return options
|
200
|
-
|
201
|
-
|
202
|
-
def read_rekordbox6_asar(rb6_install_dir: Union[str, Path]) -> str:
|
203
|
-
"""Finds and parses the Rekordbox 6 `app.asar` archive file.
|
204
|
-
|
205
|
-
An ASAR file is an archive used to package source code for an application
|
206
|
-
using Electron. Rekordbox 6 stores some useful information for opening the
|
207
|
-
new `master.db` database in this file.
|
208
|
-
|
209
|
-
Parameters
|
210
|
-
----------
|
211
|
-
rb6_install_dir : str or Path
|
212
|
-
The path of the Rekordbox 6 installation directory.
|
213
|
-
|
214
|
-
Returns
|
215
|
-
-------
|
216
|
-
asar_data : str
|
217
|
-
The data of the Rekordbox 6 `app.asar` archive file as ANSI encoded string.
|
218
|
-
"""
|
219
|
-
rb6_install_dir = Path(rb6_install_dir)
|
220
|
-
# Find location of app asar file
|
221
|
-
if sys.platform == "win32":
|
222
|
-
location = rb6_install_dir / "rekordboxAgent-win32-x64" / "resources"
|
223
|
-
encoding = "ANSI"
|
224
|
-
elif sys.platform == "darwin":
|
225
|
-
location = (
|
226
|
-
rb6_install_dir
|
227
|
-
/ "rekordbox.app"
|
228
|
-
/ "Contents"
|
229
|
-
/ "MacOS"
|
230
|
-
/ "rekordboxAgent.app"
|
231
|
-
/ "Contents"
|
232
|
-
/ "Resources"
|
233
|
-
)
|
234
|
-
encoding = "cp437"
|
235
|
-
else:
|
236
|
-
logger.warning(f"OS {sys.platform} not supported!")
|
237
|
-
return ""
|
238
|
-
|
239
|
-
# Read asar file
|
240
|
-
path = (location / "app.asar").absolute()
|
241
|
-
with open(path, "rb") as fh:
|
242
|
-
data = fh.read().decode(encoding)
|
243
|
-
return data
|
244
|
-
|
245
|
-
|
246
|
-
def _get_rb_config(
|
247
|
-
pioneer_install_dir: Path,
|
248
|
-
pioneer_app_dir: Path,
|
249
|
-
major_version: int,
|
250
|
-
) -> dict:
|
251
|
-
"""Get the program configuration for a given Rekordbox major version.
|
252
|
-
|
253
|
-
Parameters
|
254
|
-
----------
|
255
|
-
pioneer_install_dir : Path
|
256
|
-
The path of the Pioneer installation directory.
|
257
|
-
pioneer_app_dir : Path
|
258
|
-
The path of the Pioneer application data directory.
|
259
|
-
major_version : int
|
260
|
-
The major version of Rekordbox.
|
261
|
-
|
262
|
-
Returns
|
263
|
-
-------
|
264
|
-
config : dict
|
265
|
-
The program configuration.
|
266
|
-
"""
|
267
|
-
# Get latest Rekordbox installation directory for major release `major_version`
|
268
|
-
|
269
|
-
# Find all 'V.x.x' version strings in dir names
|
270
|
-
versions = list()
|
271
|
-
for p in pioneer_install_dir.iterdir():
|
272
|
-
name = p.name
|
273
|
-
if name.startswith("rekordbox"):
|
274
|
-
ver_str = name.replace("rekordbox", "").strip()
|
275
|
-
if ver_str.startswith(str(major_version)):
|
276
|
-
versions.append(ver_str)
|
277
|
-
# Get latest 'V.x.x' version string and assure there is one
|
278
|
-
versions.sort(key=lambda s: list(map(int, s.split("."))))
|
279
|
-
try:
|
280
|
-
rb_version = versions[-1]
|
281
|
-
except IndexError:
|
282
|
-
raise FileNotFoundError(
|
283
|
-
f"No Rekordbox {major_version} folder found in installation "
|
284
|
-
f"directory '{pioneer_install_dir}'"
|
285
|
-
)
|
286
|
-
# Name of the Rekordbox application directory in `pioneer_install_dir`
|
287
|
-
rb_prog_dir = pioneer_install_dir / f"rekordbox {rb_version}"
|
288
|
-
|
289
|
-
# Check installation directory
|
290
|
-
if not rb_prog_dir.exists():
|
291
|
-
raise FileNotFoundError(
|
292
|
-
f"The Rekordbox installation directory '{rb_prog_dir}' doesn't exist"
|
293
|
-
)
|
294
|
-
logger.debug("Found Rekordbox %s install-dir: '%s'", major_version, rb_prog_dir)
|
295
|
-
|
296
|
-
# Get Rekordbox application directory path for major release `major_version`
|
297
|
-
name = "rekordbox6" if major_version == 6 else "rekordbox"
|
298
|
-
rb_app_dir = pioneer_app_dir / name
|
299
|
-
if not rb_app_dir.exists():
|
300
|
-
raise FileNotFoundError(f"The directory '{rb_app_dir}' doesn't exist!")
|
301
|
-
logger.debug("Found Rekordbox %s app-dir: %s", major_version, rb_app_dir)
|
302
|
-
|
303
|
-
# Get Rekordbox database locations for major release `major_version`
|
304
|
-
settings = read_rekordbox_settings(rb_app_dir)
|
305
|
-
db_dir = Path(settings["masterDbDirectory"])
|
306
|
-
db_filename = "master.db" if major_version == 6 else "datafile.edb"
|
307
|
-
db_path = db_dir / db_filename
|
308
|
-
if not db_path.exists():
|
309
|
-
raise FileNotFoundError(f"The Rekordbox database '{db_path}' doesn't exist!")
|
310
|
-
|
311
|
-
conf = {
|
312
|
-
"version": rb_version,
|
313
|
-
"install_dir": rb_prog_dir,
|
314
|
-
"app_dir": rb_app_dir,
|
315
|
-
"db_dir": db_dir,
|
316
|
-
"db_path": db_path,
|
317
|
-
}
|
318
|
-
return conf
|
319
|
-
|
320
|
-
|
321
|
-
def _get_rb5_config(pioneer_prog_dir: Path, pioneer_app_dir: Path) -> dict:
|
322
|
-
"""Get the program configuration for Rekordbox v5.x.x."""
|
323
|
-
major_version = 5
|
324
|
-
conf = _get_rb_config(pioneer_prog_dir, pioneer_app_dir, major_version)
|
325
|
-
return conf
|
326
|
-
|
327
|
-
|
328
|
-
def _extract_pw(pioneer_install_dir: Path) -> str: # pragma: no cover
|
329
|
-
"""Extract the password for decrypting the Rekordbox 6 database key."""
|
330
|
-
try:
|
331
|
-
asar_data = read_rekordbox6_asar(pioneer_install_dir)
|
332
|
-
except FileNotFoundError as e:
|
333
|
-
logger.warning("Could not extract password: %s", e)
|
334
|
-
return ""
|
335
|
-
|
336
|
-
match_result = re.search('pass: ".(.*?)"', asar_data)
|
337
|
-
if match_result is None:
|
338
|
-
logging.warning("Incompatible rekordbox 6 database: Could not retrieve db-key.")
|
339
|
-
pw = ""
|
340
|
-
else:
|
341
|
-
match = match_result.group(0)
|
342
|
-
pw = match.replace("pass: ", "").strip('"')
|
343
|
-
return pw
|
344
|
-
|
345
|
-
|
346
|
-
def write_db6_key_cache(key: str) -> None: # pragma: no cover
|
347
|
-
"""Writes the decrypted Rekordbox6 database key to the cache file.
|
348
|
-
|
349
|
-
This method can also be used to manually cache the database key, provided
|
350
|
-
the user has found the key somewhere else. The key can be, for example,
|
351
|
-
found in some other projects that hard-coded it.
|
352
|
-
|
353
|
-
Parameters
|
354
|
-
----------
|
355
|
-
key : str
|
356
|
-
The decrypted database key. To make sure the key is valid, the first
|
357
|
-
five characters are checked before writing the key to the cache file.
|
358
|
-
The key should start with '402fd'.
|
359
|
-
|
360
|
-
Examples
|
361
|
-
--------
|
362
|
-
>>> from pyrekordbox.config import write_db6_key_cache
|
363
|
-
>>> from pyrekordbox import Rekordbox6Database
|
364
|
-
>>> write_db6_key_cache("402fd...")
|
365
|
-
>>> db = Rekordbox6Database() # The db can now be opened without providing the key
|
366
|
-
"""
|
367
|
-
# Check if the key looks like a valid key
|
368
|
-
if not key.startswith("402fd"):
|
369
|
-
raise ValueError("The provided database key doesn't look valid!")
|
370
|
-
lines = list()
|
371
|
-
lines.append(f"version: {_cache_file_version}")
|
372
|
-
lines.append("dp: " + key)
|
373
|
-
text = "\n".join(lines)
|
374
|
-
with open(_cache_file, "w") as fh:
|
375
|
-
fh.write(text)
|
376
|
-
# Set the config key to make sure the key is present after calling method
|
377
|
-
__config__["rekordbox6"]["dp"] = key
|
378
|
-
|
379
|
-
|
380
|
-
def _get_rb6_config(pioneer_prog_dir: Path, pioneer_app_dir: Path) -> dict:
|
381
|
-
"""Get the program configuration for Rekordbox v6.x.x."""
|
382
|
-
major_version = 6
|
383
|
-
conf = _get_rb_config(pioneer_prog_dir, pioneer_app_dir, major_version)
|
384
|
-
|
385
|
-
# Read Rekordbox 6 'options.json' and check db_path
|
386
|
-
opts = read_rekordbox6_options(pioneer_app_dir)
|
387
|
-
db_path = Path(opts["db-path"])
|
388
|
-
db_dir = db_path.parent
|
389
|
-
assert str(conf["db_dir"]) == str(db_dir)
|
390
|
-
assert str(conf["db_path"]) == str(db_path)
|
391
|
-
|
392
|
-
cache_version = 0
|
393
|
-
pw, dp = "", ""
|
394
|
-
if _cache_file.exists(): # pragma: no cover
|
395
|
-
# Read cache file
|
396
|
-
with open(_cache_file, "r") as fh:
|
397
|
-
text = fh.read()
|
398
|
-
lines = text.splitlines()
|
399
|
-
if lines[0].startswith("version:"):
|
400
|
-
cache_version = int(lines[0].split(":")[1].strip())
|
401
|
-
else:
|
402
|
-
cache_version = 1
|
403
|
-
if cache_version == 1:
|
404
|
-
# Cache file introduced in pyrekordbox 0.1.6 contains only the password
|
405
|
-
pw = lines[0]
|
406
|
-
elif cache_version == 2:
|
407
|
-
# Cache file introduced in pyrekordbox 0.1.7 contains version and db key
|
408
|
-
dp = lines[1].split(":")[1].strip()
|
409
|
-
else:
|
410
|
-
raise ValueError(f"Invalid cache version: {cache_version}")
|
411
|
-
|
412
|
-
if cache_version < _cache_file_version: # pragma: no cover
|
413
|
-
# Update cache file
|
414
|
-
if not pw:
|
415
|
-
logger.debug("Extracting pw")
|
416
|
-
pw = _extract_pw(conf["install_dir"])
|
417
|
-
if not dp and pw:
|
418
|
-
cipher = blowfish.Cipher(pw.encode())
|
419
|
-
dp = base64.standard_b64decode(opts["dp"])
|
420
|
-
dp = b"".join(cipher.decrypt_ecb(dp)).decode()
|
421
|
-
if dp:
|
422
|
-
write_db6_key_cache(dp)
|
423
|
-
|
424
|
-
# Add database key to config if found
|
425
|
-
if dp:
|
426
|
-
conf["dp"] = dp
|
427
|
-
|
428
|
-
return conf
|
429
|
-
|
430
|
-
|
431
|
-
# noinspection PyPackageRequirements,PyUnresolvedReferences
|
432
|
-
def _read_config_file(path: str) -> dict:
|
433
|
-
path = Path(path)
|
434
|
-
if not path.exists():
|
435
|
-
raise FileNotFoundError(f"No such file or directory: '{path}'")
|
436
|
-
|
437
|
-
ext = path.suffix.lower()
|
438
|
-
if ext == ".cfg":
|
439
|
-
from configparser import ConfigParser
|
440
|
-
|
441
|
-
parser = ConfigParser()
|
442
|
-
parser.read(path)
|
443
|
-
return parser
|
444
|
-
elif ext == ".toml":
|
445
|
-
import toml
|
446
|
-
|
447
|
-
return toml.load(path)
|
448
|
-
elif ext in (".yaml", ".yml"):
|
449
|
-
import yaml
|
450
|
-
|
451
|
-
with open(path, "r") as stream:
|
452
|
-
return yaml.safe_load(stream)
|
453
|
-
return dict()
|
454
|
-
|
455
|
-
|
456
|
-
def read_pyrekordbox_configuration():
|
457
|
-
"""Reads the pyrekordbox configuration.
|
458
|
-
|
459
|
-
So far only the `pioneer-install-dir` and `pioneer-app-dir` fileds in the
|
460
|
-
`rekordbox` section are supported. Supported configuration files are
|
461
|
-
pyproject.toml, setup.cfg, rekordbox.toml, rekordbox.cfg and rekordbox.yml.
|
462
|
-
The files are searched for in that order.
|
463
|
-
|
464
|
-
Returns
|
465
|
-
-------
|
466
|
-
pyrekordbox_config : dict
|
467
|
-
The pyrekordbox configuration data, if found.
|
468
|
-
"""
|
469
|
-
files = ["pyproject.toml", "setup.cfg"]
|
470
|
-
for ext in [".toml", ".cfg", ".yaml", ".yml"]:
|
471
|
-
files.append("pyrekordbox" + ext)
|
472
|
-
|
473
|
-
for file in files:
|
474
|
-
try:
|
475
|
-
data = _read_config_file(file)
|
476
|
-
config = dict(data["rekordbox"])
|
477
|
-
logger.debug("Read configuration from '%s'", file)
|
478
|
-
except (ImportError, FileNotFoundError) as e:
|
479
|
-
logger.debug("Could not read config file '%s': %s", file, e)
|
480
|
-
except KeyError:
|
481
|
-
pass
|
482
|
-
else:
|
483
|
-
if config:
|
484
|
-
return config
|
485
|
-
return dict()
|
486
|
-
|
487
|
-
|
488
|
-
def update_config(
|
489
|
-
pioneer_install_dir: Union[str, Path] = None,
|
490
|
-
pioneer_app_dir: Union[str, Path] = None,
|
491
|
-
):
|
492
|
-
"""Update the pyrekordbox configuration.
|
493
|
-
|
494
|
-
This method scans the system for the Rekordbox installation and application data
|
495
|
-
directories and extracts the reuired file locations. For this the default Pioneer
|
496
|
-
directories (installation and application data) are used. If the method fails to
|
497
|
-
find the directories they can be supplied as parameters. If no Rekordbox
|
498
|
-
installation is found the fileds are left unchanged.
|
499
|
-
On import configuration with the default locations is loaded.
|
500
|
-
|
501
|
-
Parameters
|
502
|
-
----------
|
503
|
-
pioneer_install_dir : str or Path, optional
|
504
|
-
The path to the Pioneer installation directory. This is where the program files
|
505
|
-
of Pioneer applications are stored. By default, the normal location of Pioneer
|
506
|
-
programs is used.
|
507
|
-
pioneer_app_dir : str or Path, optional
|
508
|
-
The path to the Pioneer application directory. This is where the application
|
509
|
-
user data of Pioneer programs is stored. By default, the normal location of
|
510
|
-
the Pioneer application data is used.
|
511
|
-
"""
|
512
|
-
# Read config file
|
513
|
-
conf = read_pyrekordbox_configuration()
|
514
|
-
if pioneer_install_dir is None and "pioneer-install-dir" in conf:
|
515
|
-
pioneer_install_dir = conf["pioneer-install-dir"]
|
516
|
-
if pioneer_app_dir is None and "pioneer-app-dir" in conf:
|
517
|
-
pioneer_app_dir = conf["pioneer-app-dir"]
|
518
|
-
|
519
|
-
# Pioneer installation directory
|
520
|
-
try:
|
521
|
-
pioneer_install_dir = get_pioneer_install_dir(pioneer_install_dir)
|
522
|
-
__config__["pioneer"]["install_dir"] = pioneer_install_dir
|
523
|
-
except FileNotFoundError as e:
|
524
|
-
logger.warning(e)
|
525
|
-
return
|
526
|
-
|
527
|
-
# Pioneer application data directory
|
528
|
-
try:
|
529
|
-
pioneer_app_dir = get_pioneer_app_dir(pioneer_app_dir)
|
530
|
-
__config__["pioneer"]["app_dir"] = pioneer_app_dir
|
531
|
-
except FileNotFoundError as e:
|
532
|
-
logger.warning(e)
|
533
|
-
return
|
534
|
-
|
535
|
-
# Update Rekordbox 5 config
|
536
|
-
try:
|
537
|
-
conf = _get_rb5_config(pioneer_install_dir, pioneer_app_dir)
|
538
|
-
__config__["rekordbox5"].update(conf)
|
539
|
-
except FileNotFoundError as e:
|
540
|
-
logger.info(e)
|
541
|
-
|
542
|
-
# Update Rekordbox 6 config
|
543
|
-
try:
|
544
|
-
conf = _get_rb6_config(pioneer_install_dir, pioneer_app_dir)
|
545
|
-
__config__["rekordbox6"].update(conf)
|
546
|
-
except FileNotFoundError as e:
|
547
|
-
logger.info(e)
|
548
|
-
|
549
|
-
|
550
|
-
# Fill the pyrekordbox-configuration
|
551
|
-
update_config()
|
552
|
-
|
553
|
-
|
554
|
-
def get_config(section: str, key: str = None):
|
555
|
-
"""Gets a section or value of the pyrekordbox configuration.
|
556
|
-
|
557
|
-
Parameters
|
558
|
-
----------
|
559
|
-
section : str
|
560
|
-
The name of the section.
|
561
|
-
key : str, optional
|
562
|
-
The name of the specific value to return. If not given all values of the section
|
563
|
-
are returned as dictionary.
|
564
|
-
|
565
|
-
Returns
|
566
|
-
-------
|
567
|
-
data : str or Path or dict
|
568
|
-
The data of a section or a specific configuration value.
|
569
|
-
"""
|
570
|
-
conf = __config__[section]
|
571
|
-
if key is None:
|
572
|
-
return conf
|
573
|
-
return conf[key]
|
574
|
-
|
575
|
-
|
576
|
-
def pformat_config(indent: str = " ", hw: int = 14, delim: str = " = ") -> str:
|
577
|
-
"""Returns a formatted string of the pyrekordbox configurations."""
|
578
|
-
pioneer = __config__["pioneer"]
|
579
|
-
rb5 = __config__["rekordbox5"]
|
580
|
-
rb6 = __config__["rekordbox6"]
|
581
|
-
|
582
|
-
lines = ["Pioneer:"]
|
583
|
-
lines += [f"{indent}{k + delim:<{hw}} {pioneer[k]}" for k in sorted(pioneer.keys())]
|
584
|
-
lines.append("Rekordbox 5:")
|
585
|
-
if rb5:
|
586
|
-
lines += [f"{indent}{k + delim:<{hw}} {rb5[k]}" for k in sorted(rb5.keys())]
|
587
|
-
lines.append("Rekordbox 6:")
|
588
|
-
if rb6:
|
589
|
-
rb6_keys = [k for k in rb6.keys() if k not in ("dp", "p")]
|
590
|
-
lines += [f"{indent}{k + delim:<{hw}} {rb6[k]}" for k in sorted(rb6_keys)]
|
591
|
-
return "\n".join(lines)
|
592
|
-
|
593
|
-
|
594
|
-
def show_config() -> None:
|
595
|
-
"""Prints a formatted string of the pyrekordbox configurations."""
|
596
|
-
print(pformat_config())
|
@@ -1,45 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
# Author: Dylan Jones
|
3
|
-
# Date: 2022-05-07
|
4
|
-
|
5
|
-
from .tables import (
|
6
|
-
AgentRegistry,
|
7
|
-
CloudAgentRegistry,
|
8
|
-
ContentActiveCensor,
|
9
|
-
ContentCue,
|
10
|
-
ContentFile,
|
11
|
-
DjmdActiveCensor,
|
12
|
-
DjmdAlbum,
|
13
|
-
DjmdArtist,
|
14
|
-
DjmdCategory,
|
15
|
-
DjmdColor,
|
16
|
-
DjmdContent,
|
17
|
-
DjmdCue,
|
18
|
-
DjmdDevice,
|
19
|
-
DjmdGenre,
|
20
|
-
DjmdHistory,
|
21
|
-
DjmdHotCueBanklist,
|
22
|
-
DjmdKey,
|
23
|
-
DjmdLabel,
|
24
|
-
DjmdMenuItems,
|
25
|
-
DjmdMixerParam,
|
26
|
-
DjmdMyTag,
|
27
|
-
DjmdPlaylist,
|
28
|
-
DjmdProperty,
|
29
|
-
DjmdRelatedTracks,
|
30
|
-
DjmdSampler,
|
31
|
-
DjmdSongHistory,
|
32
|
-
DjmdSongHotCueBanklist,
|
33
|
-
DjmdSongMyTag,
|
34
|
-
DjmdSongPlaylist,
|
35
|
-
DjmdSongRelatedTracks,
|
36
|
-
DjmdSongSampler,
|
37
|
-
DjmdSongTagList,
|
38
|
-
DjmdSort,
|
39
|
-
HotCueBanklistCue,
|
40
|
-
ImageFile,
|
41
|
-
SettingFile,
|
42
|
-
UuidIDMap,
|
43
|
-
)
|
44
|
-
|
45
|
-
from .database import Rekordbox6Database, open_rekordbox_database
|