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.
Files changed (69) hide show
  1. docs/Makefile +20 -0
  2. docs/make.bat +35 -0
  3. docs/source/_static/images/anlz_beat.svg +53 -0
  4. docs/source/_static/images/anlz_file.svg +204 -0
  5. docs/source/_static/images/anlz_pco2.svg +138 -0
  6. docs/source/_static/images/anlz_pcob.svg +148 -0
  7. docs/source/_static/images/anlz_pcp2.svg +398 -0
  8. docs/source/_static/images/anlz_pcpt.svg +263 -0
  9. docs/source/_static/images/anlz_ppth.svg +123 -0
  10. docs/source/_static/images/anlz_pqt2.svg +324 -0
  11. docs/source/_static/images/anlz_pqt2_2.svg +253 -0
  12. docs/source/_static/images/anlz_pqtz.svg +140 -0
  13. docs/source/_static/images/anlz_pssi.svg +192 -0
  14. docs/source/_static/images/anlz_pssi_entry.svg +191 -0
  15. docs/source/_static/images/anlz_pvbr.svg +125 -0
  16. docs/source/_static/images/anlz_pwav.svg +130 -0
  17. docs/source/_static/images/anlz_pwv3.svg +139 -0
  18. docs/source/_static/images/anlz_pwv4.svg +139 -0
  19. docs/source/_static/images/anlz_pwv5.svg +139 -0
  20. docs/source/_static/images/anlz_pwv5_entry.svg +100 -0
  21. docs/source/_static/images/anlz_pwv6.svg +130 -0
  22. docs/source/_static/images/anlz_pwv7.svg +139 -0
  23. docs/source/_static/images/anlz_pwvc.svg +125 -0
  24. docs/source/_static/images/anlz_tag.svg +110 -0
  25. docs/source/_static/logos/dark/logo_primary.svg +75 -0
  26. docs/source/_static/logos/light/logo_primary.svg +75 -0
  27. docs/source/_static/logos/mid/logo_primary.svg +75 -0
  28. docs/source/_templates/apidoc/module.rst_t +8 -0
  29. docs/source/_templates/apidoc/package.rst_t +57 -0
  30. docs/source/_templates/apidoc/toc.rst_t +7 -0
  31. docs/source/_templates/autosummary/class.rst +32 -0
  32. docs/source/_templates/autosummary/module.rst +55 -0
  33. docs/source/api.md +18 -0
  34. docs/source/conf.py +178 -0
  35. docs/source/development/changes.md +3 -0
  36. docs/source/development/contributing.md +3 -0
  37. docs/source/formats/anlz.md +634 -0
  38. docs/source/formats/db6.md +1233 -0
  39. docs/source/formats/mysetting.md +392 -0
  40. docs/source/formats/xml.md +376 -0
  41. docs/source/index.md +105 -0
  42. docs/source/installation.md +3 -0
  43. docs/source/quickstart.md +185 -0
  44. docs/source/requirements.txt +7 -0
  45. docs/source/tutorial/anlz.md +7 -0
  46. docs/source/tutorial/configuration.md +66 -0
  47. docs/source/tutorial/db6.md +179 -0
  48. docs/source/tutorial/index.md +20 -0
  49. docs/source/tutorial/mysetting.md +124 -0
  50. docs/source/tutorial/xml.md +140 -0
  51. pyrekordbox/__init__.py +1 -1
  52. pyrekordbox/__main__.py +16 -37
  53. pyrekordbox/_version.py +2 -2
  54. pyrekordbox/anlz/file.py +39 -0
  55. pyrekordbox/anlz/structs.py +3 -5
  56. pyrekordbox/config.py +71 -27
  57. pyrekordbox/db6/database.py +290 -61
  58. pyrekordbox/db6/registry.py +24 -0
  59. pyrekordbox/db6/tables.py +501 -340
  60. pyrekordbox/mysettings/file.py +0 -25
  61. pyrekordbox/utils.py +1 -1
  62. {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/METADATA +42 -20
  63. pyrekordbox-0.2.2.dist-info/RECORD +80 -0
  64. {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/top_level.txt +1 -0
  65. tests/test_config.py +175 -0
  66. tests/test_db6.py +95 -0
  67. pyrekordbox-0.2.0.dist-info/RECORD +0 -29
  68. {pyrekordbox-0.2.0.dist-info → pyrekordbox-0.2.2.dist-info}/LICENSE +0 -0
  69. {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/rigglemania/pysqlcipher3")
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", fix_quote=True):
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 = 'ext.extra_link_args.append("libeay32.lib")'
74
- lib_new = f'ext.extra_link_args.append("{cryptolib}")'
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
- cpath = amalgamation_src / "sqlite3.c"
83
- hpath = amalgamation_src / "sqlite3.h"
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(cpath, root / "sqlite3.c")
90
- shutil.copy2(hpath, root / "sqlite3.h")
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
- patch_pysqlcipher_setup(pysqlcipher_dir, crypto_lib, fix_quote)
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 build_amalgamation")
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 pysqlcipher3, only build the amalgamation",
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
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.2.0'
16
- __version_tuple__ = version_tuple = (0, 2, 0)
15
+ __version__ = version = '0.2.2'
16
+ __version_tuple__ = version_tuple = (0, 2, 2)