pyrekordbox 0.2.0__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/Makefile +20 -0
- docs/make.bat +35 -0
- docs/source/_static/images/anlz_beat.svg +53 -0
- docs/source/_static/images/anlz_file.svg +204 -0
- docs/source/_static/images/anlz_pco2.svg +138 -0
- docs/source/_static/images/anlz_pcob.svg +148 -0
- docs/source/_static/images/anlz_pcp2.svg +398 -0
- docs/source/_static/images/anlz_pcpt.svg +263 -0
- docs/source/_static/images/anlz_ppth.svg +123 -0
- docs/source/_static/images/anlz_pqt2.svg +324 -0
- docs/source/_static/images/anlz_pqt2_2.svg +253 -0
- docs/source/_static/images/anlz_pqtz.svg +140 -0
- docs/source/_static/images/anlz_pssi.svg +192 -0
- docs/source/_static/images/anlz_pssi_entry.svg +191 -0
- docs/source/_static/images/anlz_pvbr.svg +125 -0
- docs/source/_static/images/anlz_pwav.svg +130 -0
- docs/source/_static/images/anlz_pwv3.svg +139 -0
- docs/source/_static/images/anlz_pwv4.svg +139 -0
- docs/source/_static/images/anlz_pwv5.svg +139 -0
- docs/source/_static/images/anlz_pwv5_entry.svg +100 -0
- docs/source/_static/images/anlz_pwv6.svg +130 -0
- docs/source/_static/images/anlz_pwv7.svg +139 -0
- docs/source/_static/images/anlz_pwvc.svg +125 -0
- docs/source/_static/images/anlz_tag.svg +110 -0
- docs/source/_static/logos/dark/logo_primary.svg +75 -0
- docs/source/_static/logos/light/logo_primary.svg +75 -0
- docs/source/_static/logos/mid/logo_primary.svg +75 -0
- docs/source/_templates/apidoc/module.rst_t +8 -0
- docs/source/_templates/apidoc/package.rst_t +57 -0
- docs/source/_templates/apidoc/toc.rst_t +7 -0
- docs/source/_templates/autosummary/class.rst +32 -0
- docs/source/_templates/autosummary/module.rst +55 -0
- docs/source/api.md +18 -0
- docs/source/conf.py +178 -0
- docs/source/development/changes.md +3 -0
- docs/source/development/contributing.md +3 -0
- docs/source/formats/anlz.md +634 -0
- docs/source/formats/db6.md +1233 -0
- docs/source/formats/mysetting.md +392 -0
- docs/source/formats/xml.md +376 -0
- docs/source/index.md +105 -0
- docs/source/installation.md +3 -0
- docs/source/quickstart.md +185 -0
- docs/source/requirements.txt +7 -0
- docs/source/tutorial/anlz.md +7 -0
- docs/source/tutorial/configuration.md +66 -0
- docs/source/tutorial/db6.md +179 -0
- docs/source/tutorial/index.md +20 -0
- docs/source/tutorial/mysetting.md +124 -0
- docs/source/tutorial/xml.md +140 -0
- pyrekordbox/__init__.py +1 -1
- pyrekordbox/__main__.py +16 -37
- 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 +290 -61
- pyrekordbox/db6/registry.py +24 -0
- pyrekordbox/db6/tables.py +501 -340
- pyrekordbox/mysettings/file.py +0 -25
- pyrekordbox/utils.py +1 -1
- {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/METADATA +42 -20
- pyrekordbox-0.2.2.dist-info/RECORD +80 -0
- {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/top_level.txt +1 -0
- tests/test_config.py +175 -0
- tests/test_db6.py +95 -0
- pyrekordbox-0.2.0.dist-info/RECORD +0 -29
- {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/LICENSE +0 -0
- {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
# Configuration
|
2
|
+
|
3
|
+
Pyrekordbox looks for installed Rekordbox versions and sets up the configuration
|
4
|
+
automatically. The configuration can be checked by calling:
|
5
|
+
````python
|
6
|
+
from pyrekordbox import show_config
|
7
|
+
|
8
|
+
show_config()
|
9
|
+
````
|
10
|
+
|
11
|
+
which, for example, will print
|
12
|
+
````
|
13
|
+
Pioneer:
|
14
|
+
app_dir = C:\Users\user\AppData\Roaming\Pioneer
|
15
|
+
install_dir = C:\Program Files\Pioneer
|
16
|
+
Rekordbox 5:
|
17
|
+
app_dir = C:\Users\user\AppData\Roaming\Pioneer\rekordbox
|
18
|
+
db_dir = C:\Users\user\AppData\Roaming\Pioneer\rekordbox
|
19
|
+
db_path = C:\Users\user\AppData\Roaming\Pioneer\rekordbox\datafile.edb
|
20
|
+
install_dir = C:\Program Files\Pioneer\rekordbox 5.8.6
|
21
|
+
version = 5.8.6
|
22
|
+
Rekordbox 6:
|
23
|
+
app_dir = C:\Users\user\AppData\Roaming\Pioneer\rekordbox6
|
24
|
+
db_dir = C:\Users\user\AppData\Roaming\Pioneer\rekordbox
|
25
|
+
db_path = C:\Users\user\AppData\Roaming\Pioneer\rekordbox\master.db
|
26
|
+
dp = FJ9s0iA+hiPZg...
|
27
|
+
install_dir = C:\Program Files\Pioneer\rekordbox 6.5.3
|
28
|
+
version = 6.5.3
|
29
|
+
````
|
30
|
+
|
31
|
+
If for some reason the configuration fails the values can be updated by providing the
|
32
|
+
paths to the directory where Pioneer applications are installed (`pioneer_install_dir`)
|
33
|
+
and to the directory where Pioneer stores the application data (`pioneer_app_dir`)
|
34
|
+
````python
|
35
|
+
from pyrekordbox.config import update_config
|
36
|
+
|
37
|
+
update_config(pioneer_install_dir, pioneer_app_dir)
|
38
|
+
````
|
39
|
+
|
40
|
+
## Configuration files
|
41
|
+
Alternatively the two paths can be specified in a configuration file under the section
|
42
|
+
`rekordbox`. Supported configuration files are pyproject.toml, setup.cfg, rekordbox.toml,
|
43
|
+
rekordbox.cfg and rekordbox.yml.
|
44
|
+
|
45
|
+
**pyproject.toml** / **pyrekordbox.toml**
|
46
|
+
`````toml
|
47
|
+
[rekordbox]
|
48
|
+
pioneer-install-dir = "C:/Program Files/Pioneer"
|
49
|
+
pioneer-app-dir = "C:/Users/user/AppData/Roaming/Pioneer"
|
50
|
+
`````
|
51
|
+
|
52
|
+
|
53
|
+
**setup.cfg** / **pyrekordbox.cfg**
|
54
|
+
`````ini
|
55
|
+
[rekordbox]
|
56
|
+
pioneer-install-dir = C:/Program Files/Pioneer
|
57
|
+
pioneer-app-dir = C:/Users/user/AppData/Roaming/Pioneer
|
58
|
+
`````
|
59
|
+
|
60
|
+
|
61
|
+
**pyrekordbox.yml**
|
62
|
+
````yaml
|
63
|
+
rekordbox:
|
64
|
+
pioneer-install-dir: C:/Program Files/Pioneer
|
65
|
+
pioneer-app-dir: C:/Users/user/AppData/Roaming/Pioneer
|
66
|
+
````
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# Rekordbox 6 Database
|
2
|
+
|
3
|
+
Pyrekordbox uses [SQLALchemy](https://www.sqlalchemy.org/) as ORM-Framework to handle the
|
4
|
+
Rekordbox v6 database file (``master.db``). The table declarations can be found in
|
5
|
+
``pyrekordbox.db6.tables``.
|
6
|
+
|
7
|
+
```{seealso}
|
8
|
+
See the {ref}`Rekordbox 6 Database Format <Rekordbox 6 Database Format>` documentation for more information.
|
9
|
+
```
|
10
|
+
|
11
|
+
Since the Rekordbox v6 database handler automatically finds the ``master.db`` database file
|
12
|
+
(see configuration), it can be initialized without any arguments:
|
13
|
+
````python
|
14
|
+
from pyrekordbox import Rekordbox6Database
|
15
|
+
|
16
|
+
db = Rekordbox6Database()
|
17
|
+
````
|
18
|
+
|
19
|
+
If you are using Rekorbox v6.6.5 or later and have no cached key from a previous
|
20
|
+
Rekordbox version, the database can not be unlocked automatically.
|
21
|
+
However, the command line interface of ``pyrekordbox`` provides a command for downloading
|
22
|
+
the key from known sources and writing it to the cache file:
|
23
|
+
````shell
|
24
|
+
python -m pyrekordbox download-key
|
25
|
+
````
|
26
|
+
Once the key is cached the database can be opened without providing the key.
|
27
|
+
if you obtained the key from another source, you can also pass it to the database handler
|
28
|
+
````python
|
29
|
+
db = Rekordbox6Database(key="<insert key here>")
|
30
|
+
````
|
31
|
+
or write it to the cache file manually:
|
32
|
+
````python
|
33
|
+
from pyrekordbox.config import write_db6_key_cache
|
34
|
+
|
35
|
+
write_db6_key_cache("<insert key here>") # call once
|
36
|
+
db = Rekordbox6Database()
|
37
|
+
````
|
38
|
+
The key can be found in some other projects (see issue
|
39
|
+
[#77](https://github.com/dylanljones/pyrekordbox/issues/77)), for example [here][rb6-key].
|
40
|
+
|
41
|
+
|
42
|
+
## Querying the database
|
43
|
+
|
44
|
+
A query can be executed on any table by calling the ``query()`` method. The result is
|
45
|
+
a SQLAlchemy query object, which can be used to filter and sort the results.
|
46
|
+
````python
|
47
|
+
from pyrekordbox.db6 import tables
|
48
|
+
|
49
|
+
query = db.query(tables.DjmdContent)
|
50
|
+
results = query.filter(tables.DjmdContent.Title == "My Song").all()
|
51
|
+
````
|
52
|
+
|
53
|
+
To simplify querying the database, the ``Rekordbox6Database`` class provides simple
|
54
|
+
getters for executing queries on all the tables. The parameters of the getters are
|
55
|
+
passed to the ``query.filter_by()`` method. If the query is filtered by a *unique* key
|
56
|
+
(e.g. ``ID``), the query will be executed using the ``query.one()`` method, returning the
|
57
|
+
table instance directly:
|
58
|
+
````python
|
59
|
+
# Return specific entry in DjmDContent table
|
60
|
+
content = db.get_content(ID=0)
|
61
|
+
````
|
62
|
+
|
63
|
+
In all other cases the query is returned, allowing to further filter and sort the results:
|
64
|
+
````python
|
65
|
+
# Query and sort entries in DjmdHistory table
|
66
|
+
for history in db.get_history().order_by(tables.DjmdHistory.DateCreated):
|
67
|
+
print(history)
|
68
|
+
````
|
69
|
+
|
70
|
+
This allows the user to make use of the full power of SQLAlchemy queries.
|
71
|
+
|
72
|
+
### Relationships
|
73
|
+
|
74
|
+
Some values of table entries are linked to other tables and can not be updated
|
75
|
+
directly. For example, the [djmdContent table][djmdContent-table] contains an
|
76
|
+
``ArtistID`` column, which links to the [djmdArtist table][djmdArtist-table].
|
77
|
+
The table declarations provide relationships to access the linked values.
|
78
|
+
The artist of a song can be accessed as follows:
|
79
|
+
````python
|
80
|
+
content = db.get_content(ID=0)
|
81
|
+
artist = content.Artist
|
82
|
+
````
|
83
|
+
A full list of linked tables can be found in the [](db6-format) documentation.
|
84
|
+
|
85
|
+
|
86
|
+
## Updating the database
|
87
|
+
|
88
|
+
Many values in the Rekordbox database can be updated by simply changing the corresponding
|
89
|
+
attribute of the table instance and calling the ``db.commit()`` method:
|
90
|
+
````python
|
91
|
+
content = db.get_content().first()
|
92
|
+
content.Title = "New title"
|
93
|
+
db.commit()
|
94
|
+
````
|
95
|
+
|
96
|
+
Since some values of table entries are linked to other tables these values can not
|
97
|
+
be updated directly. Also, many values depend on other values on the table and have to
|
98
|
+
be updated accordingly to ensure consistency of the database, for example the ``TrackNo``
|
99
|
+
of songs in playlists.
|
100
|
+
|
101
|
+
To simplify updating the database, the ``Rekordbox6Database`` class provides a set of
|
102
|
+
high level methods for updating the database. These methods take care of updating all
|
103
|
+
linked values and metadata.
|
104
|
+
|
105
|
+
### Playlists
|
106
|
+
|
107
|
+
A new playlist can be created by calling the ``db.create_playlist()`` method:
|
108
|
+
````python
|
109
|
+
playlist = db.create_playlist("My Playlist")
|
110
|
+
````
|
111
|
+
It creates a new [DjmdPlaylist] instance and adds it to the [djmdPlaylist table][djmdPlaylist-table].
|
112
|
+
By default, the playlist is inserted as last element of the parent playlist folder.
|
113
|
+
Alternatively, the sequence number of the new playlist can be specified:
|
114
|
+
````python
|
115
|
+
playlist = db.create_playlist("My Playlist", seq=2)
|
116
|
+
````
|
117
|
+
To add the playlist to a specific playlist folder, the ``parent`` parameter can be used
|
118
|
+
to pass a playlist folder instance or ID:
|
119
|
+
````python
|
120
|
+
folder = db.get_playlist(Name="My Folder").one() # Query for unique playlist folder
|
121
|
+
playlist = db.create_playlist("My Playlist", parent=folder)
|
122
|
+
````
|
123
|
+
|
124
|
+
Playlist folders can also be created , which are also stored in the [djmdPlaylist table][djmdPlaylist-table].
|
125
|
+
The method accepts the same parameters as the ``db.create_playlist()`` method:
|
126
|
+
````python
|
127
|
+
folder = db.create_playlist_folder("My Folder")
|
128
|
+
````
|
129
|
+
|
130
|
+
Playlists and playlist folders can also be deleted:
|
131
|
+
````python
|
132
|
+
playlist = db.get_playlist(Name="My Playlist").one()
|
133
|
+
db.delete_playlist(playlist)
|
134
|
+
|
135
|
+
folder = db.get_playlist(Name="My Folder").one()
|
136
|
+
db.delete_playlist(folder)
|
137
|
+
````
|
138
|
+
|
139
|
+
```{caution}
|
140
|
+
When deleting a playlist or playlist folder, all children are deleted as well.
|
141
|
+
Deleting a playlist removes all songs from the playlist before deleting it. Similarly,
|
142
|
+
deleting a playlist folder removes all playlists and playlist folders in the folder,
|
143
|
+
including all songs in the sub-playlists.
|
144
|
+
```
|
145
|
+
|
146
|
+
Adding tracks to a playlist is done by calling the ``db.add_to_playlist()`` method.
|
147
|
+
It accepts a [DjmdContent] instance or corresponding ID and creates a new entry in
|
148
|
+
the [djmdSongPlaylist table][djmdSongPlaylist-table], which stores the contents of playlists:
|
149
|
+
````python
|
150
|
+
content = db.get_content(ID=0)
|
151
|
+
playlist = db.get_playlist(Name="My Playlist").one()
|
152
|
+
song = db.add_to_playlist(playlist, content)
|
153
|
+
````
|
154
|
+
|
155
|
+
To delete a song from a playlist, the [DjmdSongPlaylist] instance or ID has to be passed,
|
156
|
+
since a track can be contained in a plalyist more than once:
|
157
|
+
````python
|
158
|
+
playlist = db.get_playlist(Name="My Playlist").one()
|
159
|
+
song = playlist.Songs[0]
|
160
|
+
|
161
|
+
db.remove_from_playlist(playlist, song)
|
162
|
+
````
|
163
|
+
|
164
|
+
```{note}
|
165
|
+
More coming soon!
|
166
|
+
```
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
[db-format]: #db6-format
|
171
|
+
[djmdArtist-table]: #djmdArtist
|
172
|
+
[DjmdArtist]: pyrekordbox.db6.tables.DjmdArtist
|
173
|
+
[djmdContent-table]: #djmdContent
|
174
|
+
[DjmdContent]: pyrekordbox.db6.tables.DjmdContent
|
175
|
+
[djmdPlaylist-table]: #djmdPlaylist
|
176
|
+
[DjmdPlaylist]: pyrekordbox.db6.tables.DjmdPlaylist
|
177
|
+
[djmdSongPlaylist-table]: #djmdSongPlaylist
|
178
|
+
[DjmdSongPlaylist]: pyrekordbox.db6.tables.DjmdSongPlaylist
|
179
|
+
[rb6-key]: https://github.com/mganss/CueGen/blob/19878e6eb3f586dee0eb3eb4f2ce3ef18309de9d/CueGen/Generator.cs#L31
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Tutorial
|
2
|
+
|
3
|
+
```{attention}
|
4
|
+
Please make sure to back up your Rekordbox collection before making
|
5
|
+
any changes with pyrekordbox or developing/testing new features.
|
6
|
+
|
7
|
+
The backup dialog can be found under "File" > "Library" > "Backup Library"
|
8
|
+
```
|
9
|
+
|
10
|
+
````{toctree}
|
11
|
+
---
|
12
|
+
maxdepth: 3
|
13
|
+
---
|
14
|
+
|
15
|
+
configuration
|
16
|
+
db6
|
17
|
+
xml
|
18
|
+
anlz
|
19
|
+
mysetting
|
20
|
+
````
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# My-Settings
|
2
|
+
|
3
|
+
|
4
|
+
Rekordbox stores the user settings in ``*SETTING.DAT`` files, which get exported
|
5
|
+
to USB devices. These files are located in the ``PIONEER`` directory of a USB drive
|
6
|
+
(device exports), but are also present on local installations of Rekordbox 6.
|
7
|
+
The setting files store the settings found on the "DJ System" > "My Settings" page
|
8
|
+
of the Rekordbox preferences. These include language, LCD brightness,
|
9
|
+
tempo fader range, crossfader curve and other settings for Pioneer professional
|
10
|
+
DJ equipment.
|
11
|
+
|
12
|
+
```{seealso}
|
13
|
+
See the {ref}`My-Setting Files Format<My-Setting Files Format>` documentation for more information.
|
14
|
+
```
|
15
|
+
|
16
|
+
Pyrekordbox includes a file handler for each of the four My-Settings files. To read any of the four files, use
|
17
|
+
````python
|
18
|
+
from pyrekordbox import read_mysetting_file
|
19
|
+
|
20
|
+
file = read_mysetting_file("MYSETTING.DAT")
|
21
|
+
````
|
22
|
+
|
23
|
+
My-Settings files can also be parsed manually:
|
24
|
+
````python
|
25
|
+
from pyrekordbox import MySettingFile, MySetting2File, DjmMySettingFile, DevSettingFile
|
26
|
+
|
27
|
+
file1 = MySettingFile.parse_file("MYSETTING.DAT")
|
28
|
+
file2 = MySetting2File.parse_file("MYSETTING2.DAT")
|
29
|
+
file3 = DjmMySettingFile.parse_file("DJMMYSETTING.DAT")
|
30
|
+
file4 = DevSettingFile.parse_file("DEVSETTING.DAT")
|
31
|
+
````
|
32
|
+
|
33
|
+
After parsing a My-Setting file, the settings can be accessed as dictionary:
|
34
|
+
````python
|
35
|
+
>>> file = read_mysetting_file("MYSETTING.DAT")
|
36
|
+
>>> file["quantize"]
|
37
|
+
on
|
38
|
+
|
39
|
+
>>> file["quantize"] = "off"
|
40
|
+
````
|
41
|
+
|
42
|
+
To save the updated contents of a My-Settings file, use
|
43
|
+
````python
|
44
|
+
>>> file.save("MYSETTING.DAT")
|
45
|
+
````
|
46
|
+
|
47
|
+
## MySetting Files
|
48
|
+
|
49
|
+
The ``MYSETTING.DAT`` files store the main settings for Pioneers CDJ players.
|
50
|
+
|
51
|
+
````python
|
52
|
+
>>> file = read_mysetting_file("MYSETTING.DAT")
|
53
|
+
>>> for setting, value in file.items():
|
54
|
+
... print(f"{setting:<25} {value}")
|
55
|
+
auto_cue on
|
56
|
+
auto_cue_level memory
|
57
|
+
disc_slot_illumination bright
|
58
|
+
eject_lock unlock
|
59
|
+
hotcue_autoload on
|
60
|
+
hotcue_color off
|
61
|
+
jog_mode vinyl
|
62
|
+
jog_ring_brightness bright
|
63
|
+
jog_ring_indicator on
|
64
|
+
language english
|
65
|
+
lcd_brightness three
|
66
|
+
master_tempo off
|
67
|
+
needle_lock lock
|
68
|
+
on_air_display on
|
69
|
+
phase_meter type1
|
70
|
+
play_mode single
|
71
|
+
quantize on
|
72
|
+
quantize_beat_value one
|
73
|
+
slip_flashing on
|
74
|
+
sync off
|
75
|
+
tempo_range ten
|
76
|
+
time_mode remain
|
77
|
+
````
|
78
|
+
|
79
|
+
|
80
|
+
## MySetting2 Files
|
81
|
+
|
82
|
+
The ``MYSETTING2.DAT`` files store additional settings for Pioneers CDJ players.
|
83
|
+
|
84
|
+
````python
|
85
|
+
>>> file = read_mysetting_file("MYSETTING2.DAT")
|
86
|
+
>>> for setting, value in file.items():
|
87
|
+
... print(f"{setting:<25} {value}")
|
88
|
+
vinyl_speed_adjust touch
|
89
|
+
jog_display_mode auto
|
90
|
+
pad_button_brightness three
|
91
|
+
jog_lcd_brightness three
|
92
|
+
waveform_divisions phrase
|
93
|
+
waveform waveform
|
94
|
+
beat_jump_beat_value sixteen
|
95
|
+
````
|
96
|
+
|
97
|
+
|
98
|
+
## DjmMySetting Files
|
99
|
+
|
100
|
+
The ``DJMMYSETTING.DAT`` files store the settings for Pioneers DJMD mixers.
|
101
|
+
|
102
|
+
````python
|
103
|
+
>>> file = read_mysetting_file("DJMMYSETTING.DAT")
|
104
|
+
>>> for setting, value in file.items():
|
105
|
+
... print(f"{setting:<25} {value}")
|
106
|
+
channel_fader_curve linear
|
107
|
+
cross_fader_curve fast_cut
|
108
|
+
headphones_pre_eq post_eq
|
109
|
+
headphones_mono_split stereo
|
110
|
+
beat_fx_quantize on
|
111
|
+
mic_low_cut on
|
112
|
+
talk_over_mode advanced
|
113
|
+
talk_over_level minus_18db
|
114
|
+
midi_channel one
|
115
|
+
midi_button_type toggle
|
116
|
+
display_brightness five
|
117
|
+
indicator_brightness three
|
118
|
+
channel_fader_curve_long exponential
|
119
|
+
````
|
120
|
+
|
121
|
+
|
122
|
+
## DevSetting Files
|
123
|
+
|
124
|
+
The ``DEVSETTING.DAT`` files are not supported.
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# XML Database
|
2
|
+
|
3
|
+
We will use the Rekordbox 6 database from the test data as an example:
|
4
|
+
|
5
|
+
```python
|
6
|
+
import os
|
7
|
+
path = os.path.join(".testdata", "rekordbox 5", "database.xml")
|
8
|
+
```
|
9
|
+
|
10
|
+
An existing XML database can be parsed by passing the file path to the ``RekordboxXml``
|
11
|
+
constructor:
|
12
|
+
```python
|
13
|
+
from pyrekordbox import RekordboxXml
|
14
|
+
xml = RekordboxXml(path)
|
15
|
+
print(xml.tostring())
|
16
|
+
```
|
17
|
+
|
18
|
+
Printing the contents results in the following output:
|
19
|
+
```xml
|
20
|
+
<?xml version="1.0" encoding="utf-8"?>
|
21
|
+
<DJ_PLAYLISTS Version="1.0.0">
|
22
|
+
<PRODUCT Name="rekordbox" Version="6.6.2" Company="AlphaTheta"/>
|
23
|
+
<COLLECTION Entries="6">
|
24
|
+
<TRACK TrackID="253529738" Name="Demo Track 1" Artist="Loopmasters" Composer="" Album="" Grouping="" Genre="" Kind="Mp3-Datei " Size="6899624" TotalTime="172" DiscNumber="0" TrackNumber="0" Year="0" AverageBpm="128.00" DateAdded="2022-04-09" BitRate="320" SampleRate="44100" Comments="Tracks by www.loopmasters.com" PlayCount="0" Rating="0" Location="file://localhost/C:/Music/PioneerDJ/Demo%20Tracks/Demo%20Track%201.mp3" Remixer="" Tonality="Fm" Label="Loopmasters" Mix="">
|
25
|
+
<TEMPO Inizio="0.025" Bpm="128.00" Metro="4/4" Battito="1"/>
|
26
|
+
</TRACK>
|
27
|
+
<TRACK TrackID="17109519" Name="Demo Track 2" Artist="Loopmasters" Composer="" Album="" Grouping="" Genre="" Kind="Mp3-Datei " Size="5124342" TotalTime="128" DiscNumber="0" TrackNumber="0" Year="0" AverageBpm="120.00" DateAdded="2022-04-09" BitRate="320" SampleRate="44100" Comments="Tracks by www.loopmasters.com" PlayCount="0" Rating="0" Location="file://localhost/C:/Music/PioneerDJ/Demo%20Tracks/Demo%20Track%202.mp3" Remixer="" Tonality="Fm" Label="Loopmasters" Mix="">
|
28
|
+
<TEMPO Inizio="0.025" Bpm="120.00" Metro="4/4" Battito="1"/>
|
29
|
+
<TEMPO Inizio="48.026" Bpm="120.00" Metro="4/4" Battito="1"/>
|
30
|
+
<TEMPO Inizio="48.525" Bpm="120.00" Metro="4/4" Battito="2"/>
|
31
|
+
<TEMPO Inizio="49.026" Bpm="120.00" Metro="4/4" Battito="3"/>
|
32
|
+
<TEMPO Inizio="49.525" Bpm="120.00" Metro="4/4" Battito="4"/>
|
33
|
+
<TEMPO Inizio="50.026" Bpm="120.00" Metro="4/4" Battito="1"/>
|
34
|
+
<TEMPO Inizio="50.525" Bpm="120.00" Metro="4/4" Battito="2"/>
|
35
|
+
<TEMPO Inizio="51.026" Bpm="120.00" Metro="4/4" Battito="3"/>
|
36
|
+
<TEMPO Inizio="51.525" Bpm="120.00" Metro="4/4" Battito="4"/>
|
37
|
+
<TEMPO Inizio="52.026" Bpm="120.00" Metro="4/4" Battito="1"/>
|
38
|
+
</TRACK>
|
39
|
+
<TRACK TrackID="49557014" Name="HORN" Artist="" Composer="" Album="" Grouping="" Genre="" Kind="Wav-Datei " Size="2010816" TotalTime="7" DiscNumber="0" TrackNumber="0" Year="0" AverageBpm="0.00" DateAdded="2022-04-09" BitRate="2116" SampleRate="44100" Comments="" PlayCount="0" Rating="0" Location="file://localhost/C:/Music/PioneerDJ/Sampler/OSC_SAMPLER/PRESET%20ONESHOT/HORN.wav" Remixer="" Tonality="" Label="" Mix=""/>
|
40
|
+
<TRACK TrackID="209873516" Name="NOISE" Artist="" Composer="" Album="" Grouping="" Genre="" Kind="Wav-Datei " Size="1382226" TotalTime="5" DiscNumber="0" TrackNumber="0" Year="0" AverageBpm="0.00" DateAdded="2022-04-09" BitRate="2116" SampleRate="44100" Comments="" PlayCount="0" Rating="0" Location="file://localhost/C:/Music/PioneerDJ/Sampler/OSC_SAMPLER/PRESET%20ONESHOT/NOISE.wav" Remixer="" Tonality="" Label="" Mix=""/>
|
41
|
+
<TRACK TrackID="55231398" Name="SINEWAVE" Artist="" Composer="" Album="" Grouping="" Genre="" Kind="Wav-Datei " Size="1515258" TotalTime="5" DiscNumber="0" TrackNumber="0" Year="0" AverageBpm="0.00" DateAdded="2022-04-09" BitRate="2116" SampleRate="44100" Comments="" PlayCount="0" Rating="0" Location="file://localhost/C:/Music/PioneerDJ/Sampler/OSC_SAMPLER/PRESET%20ONESHOT/SINEWAVE.wav" Remixer="" Tonality="" Label="" Mix=""/>
|
42
|
+
<TRACK TrackID="92396897" Name="SIREN" Artist="" Composer="" Album="" Grouping="" Genre="" Kind="Wav-Datei " Size="1941204" TotalTime="7" DiscNumber="0" TrackNumber="0" Year="0" AverageBpm="0.00" DateAdded="2022-04-09" BitRate="2116" SampleRate="44100" Comments="" PlayCount="0" Rating="0" Location="file://localhost/C:/Music/PioneerDJ/Sampler/OSC_SAMPLER/PRESET%20ONESHOT/SIREN.wav" Remixer="" Tonality="" Label="" Mix=""/>
|
43
|
+
</COLLECTION>
|
44
|
+
<PLAYLISTS>
|
45
|
+
<NODE Type="0" Name="ROOT" Count="1">
|
46
|
+
<NODE Name="Trial playlist - Cloud Library Sync" Type="1" KeyType="0" Entries="0"/>
|
47
|
+
</NODE>
|
48
|
+
</PLAYLISTS>
|
49
|
+
</DJ_PLAYLISTS>
|
50
|
+
```
|
51
|
+
|
52
|
+
## Tracks
|
53
|
+
|
54
|
+
Individual tracks in the collection can be fetched by supplying an index
|
55
|
+
````
|
56
|
+
>>> xml.get_track(0)
|
57
|
+
<Track(Location=C:\Music\PioneerDJ\Demo Tracks\Demo Track 1.mp3)>
|
58
|
+
````
|
59
|
+
or the `TrackID`:
|
60
|
+
````
|
61
|
+
>>> xml.get_track(TrackID=253529738)
|
62
|
+
<Track(Location=C:\Music\PioneerDJ\Demo Tracks\Demo Track 1.mp3)>
|
63
|
+
````
|
64
|
+
|
65
|
+
All items in the collection can be fetched via
|
66
|
+
````
|
67
|
+
>>> tracks = xml.get_tracks()
|
68
|
+
>>> tracks[0]
|
69
|
+
<Track(Location=C:\Music\PioneerDJ\Demo Tracks\Demo Track 1.mp3)>
|
70
|
+
````
|
71
|
+
|
72
|
+
The XML attributes of a `Track` element are accessable as an attribute of the ``Track``
|
73
|
+
object or via a dict-interface:
|
74
|
+
````python
|
75
|
+
>>> track = xml.get_track(0)
|
76
|
+
>>> track.Name
|
77
|
+
Demo Track 1
|
78
|
+
|
79
|
+
>>> track["Name"]
|
80
|
+
Demo Track 1
|
81
|
+
````
|
82
|
+
|
83
|
+
Each track can contain a ``Tempo`` or ``PositionMark`` element. The ``Tempo`` element
|
84
|
+
stores the beat grid information:
|
85
|
+
````python
|
86
|
+
>>> track = xml.get_track(0)
|
87
|
+
>>> tempo = track.tempos[0]
|
88
|
+
>>> tempo.Bpm
|
89
|
+
128.0
|
90
|
+
````
|
91
|
+
|
92
|
+
and the ``PositionMark`` element stores the cue points of a track (not included in the XML example above):
|
93
|
+
````python
|
94
|
+
>>> track = xml.get_track(0)
|
95
|
+
>>> mark = track.marks[0]
|
96
|
+
>>> mark.Type
|
97
|
+
cue
|
98
|
+
|
99
|
+
>>> mark.Start
|
100
|
+
0.0
|
101
|
+
|
102
|
+
>>> mark.Num
|
103
|
+
-1
|
104
|
+
````
|
105
|
+
|
106
|
+
```{seealso}
|
107
|
+
See the {ref}`XML Database Format<XML Database Format>` documentation for a list of valid
|
108
|
+
attributes. All XML attributes start with a capital letter.
|
109
|
+
```
|
110
|
+
|
111
|
+
|
112
|
+
## Playlists
|
113
|
+
|
114
|
+
Playlists or playlist folders can be accessed by supplying the path:
|
115
|
+
````python
|
116
|
+
folder = xml.get_playlist("Folder")
|
117
|
+
paylist = xml.get_playlist("Folder", "Sub Playlist")
|
118
|
+
````
|
119
|
+
Content in a playlist is stored as a key, which can either be the ``TrackID`` or the
|
120
|
+
``Location`` (file path):
|
121
|
+
````python
|
122
|
+
>>> playlist.key_type
|
123
|
+
TrackID
|
124
|
+
````
|
125
|
+
|
126
|
+
The keys can be retrieved by calling
|
127
|
+
````python
|
128
|
+
keys = playlist.get_tracks()
|
129
|
+
````
|
130
|
+
|
131
|
+
A new track can be added to a playlist by specifying the corresponding key:
|
132
|
+
````python
|
133
|
+
playlist.add_track(track_key)
|
134
|
+
````
|
135
|
+
|
136
|
+
A new sub-folder or -playlist can be added by supplying the name:
|
137
|
+
````python
|
138
|
+
folder.add_playlist("Playlist")
|
139
|
+
folder.add_playlist_folder("Folder")
|
140
|
+
````
|
pyrekordbox/__init__.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# Date: 2022-04-10
|
4
4
|
|
5
5
|
from .logger import logger
|
6
|
-
from .config import show_config, get_config
|
6
|
+
from .config import show_config, get_config, update_config
|
7
7
|
from .xml import RekordboxXml, XmlDuplicateError, XmlAttributeKeyError
|
8
8
|
from .anlz import get_anlz_paths, walk_anlz_paths, read_anlz_files, AnlzFile
|
9
9
|
from .mysettings import (
|
pyrekordbox/__main__.py
CHANGED
@@ -47,31 +47,28 @@ def clone_repo(https_url: str) -> Path:
|
|
47
47
|
if not path.exists():
|
48
48
|
os.system(f"git clone {https_url}")
|
49
49
|
assert path.exists()
|
50
|
+
else:
|
51
|
+
print(f"Repository {https_url} already cloned")
|
50
52
|
return path
|
51
53
|
|
52
54
|
|
53
55
|
def clone_pysqlcipher3() -> Path:
|
54
|
-
return clone_repo(r"https://github.com/
|
56
|
+
return clone_repo(r"https://github.com/coleifer/sqlcipher3")
|
55
57
|
|
56
58
|
|
57
59
|
def clone_sqlcipher_amalgamation() -> Path:
|
58
60
|
return clone_repo(r"https://github.com/geekbrother/sqlcipher-amalgamation")
|
59
61
|
|
60
62
|
|
61
|
-
def patch_pysqlcipher_setup(pysqlcipher_dir, cryptolib="libcrypto.lib"
|
63
|
+
def patch_pysqlcipher_setup(pysqlcipher_dir, cryptolib="libcrypto.lib"):
|
62
64
|
path = Path(pysqlcipher_dir, "setup.py")
|
63
65
|
|
64
66
|
with open(path, "r") as fh:
|
65
67
|
text = fh.read()
|
66
68
|
|
67
|
-
if fix_quote:
|
68
|
-
quote_old = "if sys.platform != 'win32' else '\\\\\"'"
|
69
|
-
quote_new = ""
|
70
|
-
text = text.replace(quote_old, quote_new)
|
71
|
-
|
72
69
|
if cryptolib:
|
73
|
-
lib_old =
|
74
|
-
lib_new = f
|
70
|
+
lib_old = "os.environ.get('OPENSSL_LIBNAME') or 'libeay32.lib'"
|
71
|
+
lib_new = f"os.environ.get('OPENSSL_LIBNAME') or '{cryptolib}'"
|
75
72
|
text = text.replace(lib_old, lib_new)
|
76
73
|
|
77
74
|
with open(path, "w") as fh:
|
@@ -79,28 +76,16 @@ def patch_pysqlcipher_setup(pysqlcipher_dir, cryptolib="libcrypto.lib", fix_quot
|
|
79
76
|
|
80
77
|
|
81
78
|
def prepare_pysqlcipher(pysqlcipher_dir: Path, amalgamation_src: Path):
|
82
|
-
|
83
|
-
|
84
|
-
epath = amalgamation_src / "sqlite3ext.h"
|
85
|
-
|
86
|
-
# Create amalagamation directory
|
87
|
-
root = pysqlcipher_dir / "amalgamation"
|
79
|
+
# Copy amalgamation files to pysqlcipher directory
|
80
|
+
root = pysqlcipher_dir
|
88
81
|
root.mkdir(parents=True, exist_ok=True)
|
89
|
-
shutil.copy2(
|
90
|
-
shutil.copy2(
|
91
|
-
|
92
|
-
# Create sqlcipher directory
|
93
|
-
root = pysqlcipher_dir / "src" / "python3" / "sqlcipher"
|
94
|
-
root.mkdir(parents=True, exist_ok=True)
|
95
|
-
shutil.copy2(cpath, root / "sqlite3.c")
|
96
|
-
shutil.copy2(hpath, root / "sqlite3.h")
|
97
|
-
shutil.copy2(epath, root / "sqlite3ext.h")
|
82
|
+
shutil.copy2(amalgamation_src / "sqlite3.c", root / "sqlite3.c")
|
83
|
+
shutil.copy2(amalgamation_src / "sqlite3.h", root / "sqlite3.h")
|
98
84
|
|
99
85
|
|
100
86
|
def install_pysqlcipher(
|
101
87
|
tmpdir="pysqlcipher3",
|
102
88
|
crypto_lib="libcrypto.lib",
|
103
|
-
fix_quote=True,
|
104
89
|
pyexecutable="",
|
105
90
|
build=True,
|
106
91
|
install=True,
|
@@ -118,7 +103,9 @@ def install_pysqlcipher(
|
|
118
103
|
amalgamation_src = amalgamation_dir / "src"
|
119
104
|
|
120
105
|
prepare_pysqlcipher(pysqlcipher_dir, amalgamation_src)
|
121
|
-
|
106
|
+
if os.getenv("OPENSSL_LIBNAME") is None:
|
107
|
+
print("No OPENSSL_LIBNAME environment variable found, updating `setup.py`!")
|
108
|
+
patch_pysqlcipher_setup(pysqlcipher_dir, crypto_lib)
|
122
109
|
|
123
110
|
# Build amalgamation and install pysqlcipher
|
124
111
|
if not pyexecutable:
|
@@ -128,7 +115,7 @@ def install_pysqlcipher(
|
|
128
115
|
if build:
|
129
116
|
# Build amalgamation
|
130
117
|
print()
|
131
|
-
os.system(f"{pyexecutable} setup.py
|
118
|
+
os.system(f"{pyexecutable} setup.py build_static build")
|
132
119
|
if install:
|
133
120
|
# Install pysqlcipher package
|
134
121
|
print()
|
@@ -198,17 +185,11 @@ def main():
|
|
198
185
|
default="libcrypto.lib",
|
199
186
|
help="The name of the OpenSSl crypto libary (default: 'libcrypto.lib')",
|
200
187
|
)
|
201
|
-
install_parser.add_argument(
|
202
|
-
"-q",
|
203
|
-
"--fixquote",
|
204
|
-
action="store_false",
|
205
|
-
help="Don't fix the quotes in the pysqlcipher3 setup.py script",
|
206
|
-
)
|
207
188
|
install_parser.add_argument(
|
208
189
|
"-b",
|
209
190
|
"--buildonly",
|
210
191
|
action="store_true",
|
211
|
-
help="Don't install
|
192
|
+
help="Don't install sqlcipher3, only build the amalgamation",
|
212
193
|
)
|
213
194
|
|
214
195
|
# Parse args and handle command
|
@@ -216,9 +197,7 @@ def main():
|
|
216
197
|
if args.command == "download-key":
|
217
198
|
download_db6_key()
|
218
199
|
elif args.command == "install-sqlcipher":
|
219
|
-
install_pysqlcipher(
|
220
|
-
args.tmpdir, args.cryptolib, args.fixquote, install=not args.buildonly
|
221
|
-
)
|
200
|
+
install_pysqlcipher(args.tmpdir, args.cryptolib, install=not args.buildonly)
|
222
201
|
|
223
202
|
|
224
203
|
if __name__ == "__main__":
|
pyrekordbox/_version.py
CHANGED