pyrekordbox 0.2.2__py3-none-any.whl → 0.3.0__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/_static/images/x64dbg_rb_key.png +0 -0
- docs/source/index.md +2 -4
- docs/source/installation.md +269 -1
- docs/source/key.md +104 -0
- docs/source/quickstart.md +1 -1
- pyrekordbox/__init__.py +1 -1
- pyrekordbox/__main__.py +1 -5
- pyrekordbox/_version.py +2 -2
- pyrekordbox/anlz/file.py +2 -2
- pyrekordbox/config.py +20 -16
- pyrekordbox/db6/database.py +60 -27
- pyrekordbox/db6/registry.py +7 -1
- pyrekordbox/rbxml.py +1294 -0
- pyrekordbox/xml.py +3 -1289
- {pyrekordbox-0.2.2.dist-info → pyrekordbox-0.3.0.dist-info}/METADATA +15 -11
- {pyrekordbox-0.2.2.dist-info → pyrekordbox-0.3.0.dist-info}/RECORD +20 -17
- {pyrekordbox-0.2.2.dist-info → pyrekordbox-0.3.0.dist-info}/WHEEL +1 -1
- tests/test_xml.py +1 -1
- {pyrekordbox-0.2.2.dist-info → pyrekordbox-0.3.0.dist-info}/LICENSE +0 -0
- {pyrekordbox-0.2.2.dist-info → pyrekordbox-0.3.0.dist-info}/top_level.txt +0 -0
pyrekordbox/db6/database.py
CHANGED
@@ -12,7 +12,6 @@ from sqlalchemy import create_engine, or_, event, MetaData
|
|
12
12
|
from sqlalchemy.orm import Session
|
13
13
|
from sqlalchemy.exc import NoResultFound
|
14
14
|
from sqlalchemy.sql.sqltypes import DateTime, String
|
15
|
-
import packaging.version
|
16
15
|
from ..utils import get_rekordbox_pid
|
17
16
|
from ..config import get_config
|
18
17
|
from ..anlz import get_anlz_paths, read_anlz_files
|
@@ -23,24 +22,22 @@ from . import tables
|
|
23
22
|
|
24
23
|
try:
|
25
24
|
from sqlcipher3 import dbapi2 as sqlite3 # noqa
|
25
|
+
|
26
|
+
_sqlcipher_available = True
|
26
27
|
except ImportError:
|
27
28
|
import sqlite3
|
28
29
|
|
29
|
-
|
30
|
+
_sqlcipher_available = False
|
31
|
+
|
32
|
+
MAX_VERSION = "6.6.5"
|
30
33
|
|
31
34
|
logger = logging.getLogger(__name__)
|
32
35
|
|
33
36
|
rb6_config = get_config("rekordbox6")
|
34
37
|
|
35
38
|
|
36
|
-
class
|
37
|
-
|
38
|
-
super().__init__(
|
39
|
-
f"Incompatible rekordbox 6 version\n"
|
40
|
-
f"Your are using rekordbox {rb_version} but the key extraction only works "
|
41
|
-
f"for versions lower than {MAX_VERSION}.\n"
|
42
|
-
"Please use the `key` parameter to manually provide the database key."
|
43
|
-
)
|
39
|
+
class NoCachedKey(Exception):
|
40
|
+
pass
|
44
41
|
|
45
42
|
|
46
43
|
def open_rekordbox_database(path=None, key="", unlock=True, sql_driver=None):
|
@@ -109,13 +106,16 @@ def open_rekordbox_database(path=None, key="", unlock=True, sql_driver=None):
|
|
109
106
|
|
110
107
|
if unlock:
|
111
108
|
if not key:
|
112
|
-
ver = packaging.version.parse(rb6_config["version"])
|
113
|
-
if ver >= MAX_VERSION:
|
114
|
-
raise IncompatibleVersionError(rb6_config["version"])
|
115
109
|
try:
|
116
110
|
key = rb6_config["dp"]
|
117
111
|
except KeyError:
|
118
|
-
raise
|
112
|
+
raise NoCachedKey(
|
113
|
+
"Could not unlock database: No key found\n"
|
114
|
+
f"If you are using Rekordbox>{MAX_VERSION} the key can not be "
|
115
|
+
f"extracted automatically!\n"
|
116
|
+
"Please use the CLI of pyrekordbox to download the key or "
|
117
|
+
"use the `key` parameter to manually provide the database key."
|
118
|
+
)
|
119
119
|
logger.info("Key: %s", key)
|
120
120
|
# Unlock database
|
121
121
|
con.execute(f"PRAGMA key='{key}'")
|
@@ -207,14 +207,26 @@ class Rekordbox6Database:
|
|
207
207
|
raise FileNotFoundError(f"File '{path}' does not exist!")
|
208
208
|
# Open database
|
209
209
|
if unlock:
|
210
|
-
if
|
211
|
-
|
210
|
+
if not _sqlcipher_available:
|
211
|
+
raise ImportError(
|
212
|
+
"Could not unlock database: 'sqlcipher3' package not found"
|
213
|
+
)
|
212
214
|
if not key:
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
215
|
+
try:
|
216
|
+
key = rb6_config["dp"]
|
217
|
+
except KeyError:
|
218
|
+
raise NoCachedKey(
|
219
|
+
"Could not unlock database: No key found\n"
|
220
|
+
f"If you are using Rekordbox>{MAX_VERSION} the key cannot be "
|
221
|
+
f"extracted automatically!\n"
|
222
|
+
"Please use the CLI of pyrekordbox to download the key or "
|
223
|
+
"use the `key` parameter to manually provide it."
|
224
|
+
)
|
225
|
+
else:
|
226
|
+
# Check if key looks like a valid key
|
227
|
+
if not key.startswith("402fd"):
|
228
|
+
raise ValueError("The provided database key doesn't look valid!")
|
229
|
+
|
218
230
|
logger.info("Key: %s", key)
|
219
231
|
# Unlock database and create engine
|
220
232
|
url = f"sqlite+pysqlcipher://:{key}@/{path}?"
|
@@ -1815,7 +1827,9 @@ class Rekordbox6Database:
|
|
1815
1827
|
root = self.get_anlz_dir(content)
|
1816
1828
|
return read_anlz_files(root)
|
1817
1829
|
|
1818
|
-
def update_content_path(
|
1830
|
+
def update_content_path(
|
1831
|
+
self, content, path, save=True, check_path=True, commit=True
|
1832
|
+
):
|
1819
1833
|
"""Update the file path of a track in the Rekordbox v6 database.
|
1820
1834
|
|
1821
1835
|
This changes the `FolderPath` entry in the ``DjmdContent`` table and the
|
@@ -1832,6 +1846,8 @@ class Rekordbox6Database:
|
|
1832
1846
|
If True, the changes made are written to disc.
|
1833
1847
|
check_path : bool, optional
|
1834
1848
|
If True, raise an assertion error if the given file path does not exist.
|
1849
|
+
commit : bool, optional
|
1850
|
+
If True, the changes are committed to the database. True by default.
|
1835
1851
|
|
1836
1852
|
Examples
|
1837
1853
|
--------
|
@@ -1848,7 +1864,7 @@ class Rekordbox6Database:
|
|
1848
1864
|
Updating the path changes the database entry
|
1849
1865
|
|
1850
1866
|
>>> new_path = "C:/Music/PioneerDJ/Sampler/PRESET ONESHOT/NOISE.wav"
|
1851
|
-
>>> db.update_content_path(cont,
|
1867
|
+
>>> db.update_content_path(cont, path)
|
1852
1868
|
>>> cont.FolderPath
|
1853
1869
|
C:/Music/PioneerDJ/Sampler/PRESET ONESHOT/NOISE.wav
|
1854
1870
|
|
@@ -1882,15 +1898,31 @@ class Rekordbox6Database:
|
|
1882
1898
|
logger.debug("Updating database file path: %s", path)
|
1883
1899
|
content.FolderPath = path
|
1884
1900
|
|
1901
|
+
# Update the OrgFolderPath column with the new path
|
1902
|
+
# if the column matches the old_path variable
|
1903
|
+
org_folder_path = content.OrgFolderPath
|
1904
|
+
if org_folder_path == old_path:
|
1905
|
+
content.OrgFolderPath = path
|
1906
|
+
|
1907
|
+
# Update the FileNameL column with the new filename if it changed
|
1908
|
+
new_name = path.split("/")[-1]
|
1909
|
+
if content.FileNameL != new_name:
|
1910
|
+
content.FileNameL = new_name
|
1911
|
+
|
1885
1912
|
if save:
|
1886
|
-
logger.debug("Saving
|
1913
|
+
logger.debug("Saving ANLZ files")
|
1887
1914
|
# Save ANLZ files
|
1888
1915
|
for anlz_path, anlz in anlz_files.items():
|
1889
1916
|
anlz.save(anlz_path)
|
1917
|
+
|
1918
|
+
if commit:
|
1890
1919
|
# Commit database changes
|
1920
|
+
logger.debug("Committing changes to the database")
|
1891
1921
|
self.commit()
|
1892
1922
|
|
1893
|
-
def update_content_filename(
|
1923
|
+
def update_content_filename(
|
1924
|
+
self, content, name, save=True, check_path=True, commit=True
|
1925
|
+
):
|
1894
1926
|
"""Update the file name of a track in the Rekordbox v6 database.
|
1895
1927
|
|
1896
1928
|
This changes the `FolderPath` entry in the ``DjmdContent`` table and the
|
@@ -1907,6 +1939,8 @@ class Rekordbox6Database:
|
|
1907
1939
|
If True, the changes made are written to disc.
|
1908
1940
|
check_path : bool, optional
|
1909
1941
|
If True, raise an assertion error if the new file path does not exist.
|
1942
|
+
commit : bool, optional
|
1943
|
+
If True, the changes are committed to the database. True by default.
|
1910
1944
|
|
1911
1945
|
See Also
|
1912
1946
|
--------
|
@@ -1932,7 +1966,6 @@ class Rekordbox6Database:
|
|
1932
1966
|
>>> file = list(files.values())[0]
|
1933
1967
|
>>> cont.FolderPath == file.get("path")
|
1934
1968
|
True
|
1935
|
-
|
1936
1969
|
"""
|
1937
1970
|
if isinstance(content, (int, str)):
|
1938
1971
|
content = self.get_content(ID=content)
|
@@ -1941,7 +1974,7 @@ class Rekordbox6Database:
|
|
1941
1974
|
ext = old_path.suffix
|
1942
1975
|
new_path = old_path.parent / name
|
1943
1976
|
new_path = new_path.with_suffix(ext)
|
1944
|
-
self.update_content_path(content, new_path, save, check_path)
|
1977
|
+
self.update_content_path(content, new_path, save, check_path, commit=commit)
|
1945
1978
|
|
1946
1979
|
def to_dict(self, verbose=False):
|
1947
1980
|
"""Convert the database to a dictionary.
|
pyrekordbox/db6/registry.py
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
import logging
|
6
6
|
from contextlib import contextmanager
|
7
|
+
from sqlalchemy.orm.exc import ObjectDeletedError
|
7
8
|
|
8
9
|
logger = logging.getLogger(__name__)
|
9
10
|
|
@@ -72,7 +73,12 @@ class RekordboxAgentRegistry:
|
|
72
73
|
The table entry instance.
|
73
74
|
"""
|
74
75
|
if cls.__enabled__:
|
75
|
-
|
76
|
+
try:
|
77
|
+
s = str(instance)
|
78
|
+
logger.debug("On delete: %s", s)
|
79
|
+
except ObjectDeletedError:
|
80
|
+
instance = []
|
81
|
+
|
76
82
|
cls.__update_sequence__.append((instance, "delete", "", ""))
|
77
83
|
|
78
84
|
@classmethod
|