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/index.md CHANGED
@@ -11,7 +11,6 @@ sphinx-quickstart on Thu Apr 7 15:06:50 2022.
11
11
  [![Python][python-badge+]][pypi-link]
12
12
  [![Platform][platform-badge]][pypi-link]
13
13
  [![license: MIT][license-badge]][license-link]
14
- [![style: black][black-badge]][black-link]
15
14
 
16
15
  ```{admonition} Disclaimer
17
16
  This project is not affiliated with Pioneer Corp. or its related companies
@@ -45,6 +44,7 @@ caption: User Guide
45
44
 
46
45
  quickstart
47
46
  installation
47
+ key
48
48
  tutorial/index
49
49
  api
50
50
  ```
@@ -90,16 +90,14 @@ PyPI <https://pypi.org/project/pyrekordbox/>
90
90
  [tests-badge]: https://img.shields.io/github/actions/workflow/status/dylanljones/pyrekordbox/tests.yml?branch=master&label=tests&logo=github&style=flat
91
91
  [codecov-badge]: https://codecov.io/gh/dylanljones/pyrekordbox/branch/master/graph/badge.svg?token=5Z2KVGL7N3
92
92
  [python-badge]: https://img.shields.io/pypi/pyversions/pyrekordbox?style=flat
93
- [python-badge+]: https://img.shields.io/badge/python-3.7+-blue.svg
93
+ [python-badge+]: https://img.shields.io/badge/python-3.8+-blue.svg
94
94
  [platform-badge]: https://img.shields.io/badge/platform-win%20%7C%20osx-blue?style=flat
95
95
  [pypi-badge]: https://img.shields.io/pypi/v/pyrekordbox?style=flat
96
96
  [license-badge]: https://img.shields.io/pypi/l/pyrekordbox?color=lightgrey
97
- [black-badge]: https://img.shields.io/badge/code%20style-black-000000?style=flat
98
97
 
99
98
  [pypi-link]: https://pypi.org/project/pyrekordbox/
100
99
  [license-link]: https://github.com/dylanljones/pyrekordbox/blob/master/LICENSE
101
100
  [tests-link]: https://github.com/dylanljones/pyrekordbox/actions/workflows/tests.yml
102
- [black-link]: https://github.com/psf/black
103
101
  [codecov-link]: https://app.codecov.io/gh/dylanljones/pyrekordbox/tree/master
104
102
 
105
103
  [issue]: https://github.com/dylanljones/pyrekordbox/issues/64
@@ -1,3 +1,271 @@
1
- ```{include} ../../INSTALLATION.md
1
+ # Installation
2
2
 
3
+ Pyrekordbox is available on [PyPI]:
4
+
5
+ ```sh
6
+ $ pip install pyrekordbox
7
+ ```
8
+
9
+ Alternatively, it can be installed via [GitHub]:
10
+
11
+ ```sh
12
+ $ pip install git+https://github.com/dylanljones/pyrekordbox.git@VERSION
13
+ ```
14
+
15
+ where `VERSION` is a branch, tag or release. The project can also be cloned/forked
16
+ and installed via
17
+
18
+ ```sh
19
+ $ pip install .
20
+ ```
21
+
22
+
23
+ ## Installing SQLCipher
24
+
25
+ Unlocking the new Rekordbox 6 `master.db` database file requires [SQLCipher][sqlcipher].
26
+ Pyrekordbox makes no attempt to download/install SQLCipher, as it is a
27
+ pure Python package - whereas the SQLCipher/sqlcipher3 installation is
28
+ platform-dependent and can not be installed easily via ``pip``.
29
+
30
+ [sqlcipher3] can either be built with the system [SQLCipher][sqlcipher] or built against
31
+ a statically linked amalgamation of the SQLite3 source code.
32
+
33
+ ### Windows
34
+
35
+ #### SQLCipher Amalagamation
36
+
37
+ The easiest method to install SQLCipher on Windows is to build [sqlcipher3]
38
+ against an amalgamation of the SQLite3 source code.
39
+
40
+ 1. **Install [Visual Studio Community Edition][VS]**
41
+
42
+ Make sure to select all the GCC options (VC++, C++, etc) in the installation process.
43
+ The following workloads under ``Desktop & Mobile`` should be sufficient:
44
+ - Desktop Development with C++
45
+ - .NET desktop development
46
+
47
+
48
+ 2. **Install a prebuilt [OpenSSL binary][OpenSSL]**
49
+
50
+ Choose the latest Win32/Win64 version. Make sure to download the full version,
51
+ not the light version.
52
+
53
+
54
+ 3. **Confirm that the `OPENSSL_CONF` environment variable is set properly in environment variables**
55
+
56
+ This should not be root openssl path (ex: `C:/Program Files/openssl-Win64`),
57
+ but instead should be the path to the config file, for example:
58
+ - 32-bit: ``C:/Program Files (x86)/openssl-Win32/bin/openssl.cfg``
59
+ - 64-bit: ``C:/Program Files/openssl-Win64/bin/openssl.cfg``
60
+
61
+ ```{note}
62
+ The library names of OpenSSL have changed in version 1.1.0 (see [this](https://stackoverflow.com/questions/65345077/unable-to-build-sqlcipher3-on-windows) discussion).
63
+ If you are using a newer version, you can set an environment variable
64
+ ``OPENSSL_LIBNAME`` to the name of the library, e.g. ``libcrypto.lib``.
65
+ Alternatively, you can modify the ``setup.py`` script (see step 8 below).
66
+
67
+ You might have to restart Windows for the changes to take effect.
68
+ ```
69
+
70
+ 4. **Copy the openssl folder to the Microsoft Visual Studio 14 VC include directory**
71
+
72
+ The openssl folder can be found here:
73
+ - 32-bit: `C:/Program Files (x86)/OpenSSL-Win32/include/openssl`
74
+ - 64-bit: `C:/Program Files/OpenSSL-Win64/include/openssl`
75
+
76
+ The VC include directory can be found in the
77
+ Visual Studio 14 installation directory:
78
+
79
+ > C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include
80
+
81
+ In newer versions it might aso be in
82
+
83
+ > C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/[version]/include
84
+
85
+ Confirm the following path exists `.../include/openssl/aes.h`
86
+
87
+
88
+ 5. **Download / compile the SQLCipher 3 amalgamation files**
89
+
90
+ Pre-built SQLCipher amalgamation files can be downloaded from [this repo][amalgamation]:
91
+ ````shell
92
+ git clone https://github.com/geekbrother/sqlcipher-amalgamation
93
+ ````
94
+ To compile the amalgamation files on your own, follow [this tutorial](http://www.jerryrw.com/howtocompile.php).
95
+
96
+
97
+ 6. **Clone [sqlcipher3] into any directory**
98
+
99
+ ````shell
100
+ git clone https://github.com/coleifer/sqlcipher3
101
+ ````
102
+
103
+
104
+ 7. **Copy amalgamation files to the `sqlcipher3` directory**
105
+
106
+ Copy files ``sqlite3.c`` and ``sqlite3.h`` from the amalgamation directory from step 5
107
+ to the root of the ``sqlcipher3`` directory from step 6.
108
+ ````shell
109
+ Copy-Item -Path 'sqlcipher-amalgamation/src/sqlite3.c' -Destination "sqlcipher3/"
110
+ Copy-Item -Path 'sqlcipher-amalgamation/src/sqlite3.h' -Destination "sqlcipher3/"
111
+ ````
112
+
113
+
114
+ 8. **Modify the ``sqlcipher3/setup.py`` script (optional)**
115
+
116
+ If building the amalgamation fails and you haven't set the ``OPENSSL_LIBNAME``
117
+ environment variable in step 3, you have to modify the ``setup.py`` script. Change
118
+ ````python
119
+ openssl_libname = os.environ.get('OPENSSL_LIBNAME') or 'libeay32.lib'
120
+ ````
121
+ to
122
+ ````python
123
+ openssl_libname = os.environ.get('OPENSSL_LIBNAME') or 'libcrypto.lib'
124
+ ````
125
+
126
+
127
+ 9. **Build using the amalgamation and install**
128
+
129
+ ``cd`` into the ``sqlcipher3`` directory and run
130
+ ````shell
131
+ python setup.py build_static build
132
+ python setup.py install
133
+ ````
134
+
135
+
136
+ You now should have a working ``sqlcipher3`` installation! The directory of the
137
+ cloned ``sqlcipher3`` repo can be deleted after installing the package.
138
+
139
+ Steps 5-9 can be automated using the CLI of ``pyrekordbox``:
140
+
141
+ > python3 -m pyrekordbox install-sqlcipher --help
142
+ usage: pyrekordbox install-sqlcipher [-h] [-t TMPDIR] [-l CRYPTOLIB] [-q] [-b]
143
+
144
+ -t TMPDIR, --tmpdir TMPDIR
145
+ Path for storing temporary data (default: '.tmp')
146
+ -l CRYPTOLIB, --cryptolib CRYPTOLIB
147
+ The name of the OpenSSl crypto libary (default: 'libcrypto.lib')
148
+ -b, --buildonly Don't install sqlcipher3, only build the amalgamation
149
+
150
+
151
+ ##### Troubleshooting
152
+
153
+ - **Microsoft Visual C++ error**
154
+
155
+ If you are getting an error like
156
+ ````shell
157
+ error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools"``
158
+ ````
159
+ and have Visual Studio installed, you might not have all the necessary C/C++ components.
160
+
161
+ - **LINK error**
162
+
163
+ If you are getting an error like
164
+ ````shell
165
+ LINK : fatal error LNK1158: cannot run 'rc.exe'
166
+ ````
167
+ or
168
+ ````shell
169
+ LINK : fatal error LNK1327: failure during running rc.exe
170
+ ````
171
+ make sure all the necessary C/C++ components are installed and that you have selected
172
+ the latest Win 10/11 SDK in the Visual Studio installer. If you are still getting the error,
173
+ follow the suggestions in this [StackOverflow post](https://stackoverflow.com/questions/14372706/visual-studio-cant-build-due-to-rc-exe).
174
+
175
+
176
+ ### MacOS
177
+
178
+
179
+ #### System SQLCipher
180
+
181
+ For building [sqlcipher3] against the system SQLCipher installation on MacOS follow these steps:
182
+
183
+ 1) Install [Homebrew](https://brew.sh) if you do not have it on your machine.
184
+ 2) Install SQLCipher with `brew install SQLCipher`.
185
+ 3) With the python environment you are using to run pyrekordbox active execute the following:
186
+ ```shell
187
+ git clone https://github.com/coleifer/sqlcipher3
188
+ cd sqlcipher3
189
+ SQLCIPHER_PATH=$(brew info sqlcipher | awk 'NR==4 {print $1; exit}'); C_INCLUDE_PATH="$SQLCIPHER_PATH"/include LIBRARY_PATH="$SQLCIPHER_PATH"/lib python setup.py build
190
+ SQLCIPHER_PATH=$(brew info sqlcipher | awk 'NR==4 {print $1; exit}'); C_INCLUDE_PATH="$SQLCIPHER_PATH"/include LIBRARY_PATH="$SQLCIPHER_PATH"/lib python setup.py install
191
+ ```
192
+ Make sure the `C_INCLUDE` and `LIBRARY_PATH` point to the installed SQLCipher path. It may differ on your machine.
193
+
194
+ ````{note}
195
+ If you are having issues building sqlcipher on M1 Macs you might have to add some symlinks:
196
+ ```shell
197
+ ln -s /opt/homebrew/lib/libsqlcipher.a /usr/local/lib/libsqlcipher.a
198
+ ln -s /opt/homebrew/include/sqlcipher /usr/local/include/sqlcipher
3
199
  ```
200
+ ````
201
+ #### SQLCipher Amalagamation
202
+
203
+ You can also build [sqlcipher3] against an amalgamation on MacOS.
204
+
205
+ 1. **Download / compile the SQLCipher amalgamation files**
206
+
207
+ Pre-built SQLCipher amalgamation files can be downloaded from [this repo][amalgamation]:
208
+ ````shell
209
+ git clone https://github.com/geekbrother/sqlcipher-amalgamation
210
+ ````
211
+ You can also build the amalagamtion files on your own.
212
+
213
+
214
+ 2. **Clone [sqlcipher3] into any directory**
215
+
216
+ ````shell
217
+ git clone https://github.com/coleifer/sqlcipher3
218
+ ````
219
+
220
+
221
+ 3. **Copy amalgamation files to the `sqlcipher3` directory**
222
+
223
+ Copy files ``sqlite3.c`` and ``sqlite3.h`` from the amalgamation directory from step 1
224
+ to the root of the ``sqlcipher3`` directory from step 2.
225
+ ````shell
226
+ cp sqlcipher-amalgamation/src/sqlite3.[ch] sqlcipher3/
227
+ ````
228
+
229
+ 4. **Build using the amalgamation and install**
230
+
231
+ ``cd`` into the ``sqlcipher3`` directory and run
232
+ ````shell
233
+ python setup.py build_static build
234
+ python setup.py install
235
+ ````
236
+
237
+ The steps above can be automated using the CLI of ``pyrekordbox``
238
+
239
+ > python3 -m pyrekordbox install-sqlcipher --help
240
+ usage: pyrekordbox install-sqlcipher [-h] [-t TMPDIR] [-l CRYPTOLIB] [-q] [-b]
241
+
242
+ -t TMPDIR, --tmpdir TMPDIR
243
+ Path for storing temporary data (default: '.tmp')
244
+ -b, --buildonly Don't install sqlcipher3, only build the amalgamation
245
+
246
+
247
+ ```{note}
248
+ The `CRYPTOLIB` argument is only used on Windows
249
+ ```
250
+
251
+
252
+ ### Using SQLCipher
253
+
254
+ After the installation SQLCipher-databases can be unlocked via the `sqlcipher3` package:
255
+ ````python
256
+ from sqlcipher3 import dbapi2 as sqlite3
257
+
258
+ conn = sqlite3.connect('test.db')
259
+ c = conn.cursor()
260
+ c.execute("PRAGMA key='password'")
261
+ ````
262
+
263
+
264
+
265
+ [VS]: https://visualstudio.microsoft.com/de/vs/community/
266
+ [OpenSSL]: https://slproweb.com/products/Win32OpenSSL.html
267
+ [sqlcipher3]: https://github.com/coleifer/sqlcipher3
268
+ [amalgamation]: https://github.com/geekbrother/sqlcipher-amalgamation
269
+ [Pypi]: https://pypi.org/project/pyrekordbox/
270
+ [GitHub]: https://github.com/dylanljones/pyrekordbox
271
+ [sqlcipher]: https://www.zetetic.net/sqlcipher/open-source/
docs/source/key.md ADDED
@@ -0,0 +1,104 @@
1
+ # Database key
2
+
3
+ If you are using Rekordbox v6.6.5 or later and have no cached key from a previous
4
+ Rekordbox version, the database can not be unlocked automatically.
5
+ However, the command line interface of ``pyrekordbox`` provides a command for downloading
6
+ the key from known sources and writing it to the cache file:
7
+ ````shell
8
+ python -m pyrekordbox download-key
9
+ ````
10
+ Once the key is cached the database can be opened without providing the key.
11
+ if you obtained the key from another source, you can also pass it to the database handler
12
+ ````python
13
+ db = Rekordbox6Database(key="<insert key here>")
14
+ ````
15
+ or write it to the cache file manually:
16
+ ````python
17
+ from pyrekordbox.config import write_db6_key_cache
18
+
19
+ write_db6_key_cache("<insert key here>") # call once
20
+ db = Rekordbox6Database()
21
+ ````
22
+
23
+ ## Alternative methods
24
+
25
+ The key can be extracted manually from the users machine. After the key is obtained
26
+ it can be writen to the cache file as described above.
27
+ The method varies depending on the operating system.
28
+
29
+ ### Windows
30
+
31
+ On Windows, the key can be extracted from the Rekordbox executable using a debugger:
32
+
33
+ 1. Download [x64dbg] and run it.
34
+
35
+ 2. Options -> Preferences. Make sure "Entry Breakpoint" is set in the Events tab.
36
+
37
+ 3. File -> Open... `rekordbox.exe` (the main application executable)
38
+
39
+ 4. Look at the status bar. It should have a yellow "Paused" icon followed by some status text.
40
+ Right now it should say "System breakpoint reached!"
41
+
42
+ 5. Hit F9 or press the Run button in the top bar. The status text should change to
43
+ "INT3 breakpoint 'entry breakpoint' at <rekordbox.EntryPoint>".
44
+
45
+ 6. Click in the disassembly window, then press Ctrl+G to open the Go To Expression box,
46
+ and search for `sqlite3_key_v2` and press OK. This should jump you to the code for
47
+ that function, which typically starts with `mov dword ptr ss:[rsp+xx], r9d` or similar.
48
+
49
+ 7. Without clicking anywhere on the disassembly window, press F2 to toggle breakpoint.
50
+ The top instruction's address should turn red.
51
+
52
+ 8. Hit F9 or press the Run button in the top bar. The status text will start changing
53
+ a bunch, while the program starts up. Wait until the status bar goes back to "Paused"
54
+ in yellow. If the status text says something like "First chance exception on..."
55
+ press F9 again.
56
+
57
+ 9. The status bar should go to "Paused" in yellow again, this time with status text
58
+ that says "INT3 breakpoint at <sqlite3.sqlite3_key_v2>". This means our breakpoint
59
+ has been hit.
60
+
61
+ 10. Click the register panel (top right, where RAX, RBX, RCX, etc. are listed) so
62
+ it updates. Right click the red address next to R8, and click "Follow in dump".
63
+
64
+ 11. The dump at the bottom left will move to that address. Right click the dump panel
65
+ and select Text -> ASCII at the bottom. You should now see the key as a string.
66
+ You can drag-select it, then right click to copy selected line.
67
+
68
+ 12. Go to Debug -> Close to close the process, then close x64dbg.
69
+
70
+
71
+ ```{figure} /_static/images/x64dbg_rb_key.png
72
+ :align: center
73
+ :scale: 60
74
+ ```
75
+
76
+
77
+ ### MacOS
78
+
79
+ On MacOS, the key can be extracted using the [RekordLocksmith] tool:
80
+
81
+ 1. Install LLDB:
82
+ - LLDB can be installed with Xcode on macOS via the App Store or xcode-cli-commands.
83
+ - Ensure LLDB is accessible from the terminal by running `lldb` in the terminal.
84
+
85
+ 2. Disable SIP:
86
+ - Restart your Mac and hold down `Command-R` as it boots to enter Recovery Mode.
87
+ - Open the Terminal from the Utilities menu.
88
+ - Type `csrutil disable` and press Enter.
89
+ - Restart your Mac.
90
+
91
+ 3. Download RekordLocksmith:
92
+ - Clone or download the [RekordLocksmith] repository from GitHub.
93
+
94
+ 4. Run RekordLocksmith:
95
+ - Use the terminal to navigate to the folder containing `rekordlocksmith.py`.
96
+ - Run the script:
97
+ ````shell
98
+ python3 rekordlocksmith.py /Applications/rekordbox\ 6/rekordbox.app/Contents/MacOS/rekordbox
99
+ ````
100
+ The tool will output the database key to the terminal and save it to a file named `rekordbox_db_pass.txt` in the current directory.
101
+
102
+
103
+ [x64dbg]: https://x64dbg.com/
104
+ [RekordLocksmith]: https://github.com/Bide-UK/rekordlocksmith#rekordlocksmith
docs/source/quickstart.md CHANGED
@@ -101,7 +101,7 @@ between two databases.
101
101
  Pyrekordbox can read and write Rekordbox XML databases.
102
102
 
103
103
  ````python
104
- from pyrekordbox.xml import RekordboxXml
104
+ from pyrekordbox.rbxml import RekordboxXml
105
105
 
106
106
  xml = RekordboxXml("database.xml")
107
107
 
pyrekordbox/__init__.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  from .logger import logger
6
6
  from .config import show_config, get_config, update_config
7
- from .xml import RekordboxXml, XmlDuplicateError, XmlAttributeKeyError
7
+ from .rbxml import RekordboxXml, XmlDuplicateError, XmlAttributeKeyError
8
8
  from .anlz import get_anlz_paths, walk_anlz_paths, read_anlz_files, AnlzFile
9
9
  from .mysettings import (
10
10
  get_mysetting_paths,
pyrekordbox/__main__.py CHANGED
@@ -91,10 +91,6 @@ def install_pysqlcipher(
91
91
  install=True,
92
92
  cleanup=True,
93
93
  ):
94
- if sys.platform != "win32":
95
- print("Not on Windows, aborting...")
96
- return
97
-
98
94
  tmpdir = Path(tmpdir)
99
95
  # Download pysqlcipher3 and prepare amalgamation build
100
96
  with WorkingDir(tmpdir):
@@ -103,7 +99,7 @@ def install_pysqlcipher(
103
99
  amalgamation_src = amalgamation_dir / "src"
104
100
 
105
101
  prepare_pysqlcipher(pysqlcipher_dir, amalgamation_src)
106
- if os.getenv("OPENSSL_LIBNAME") is None:
102
+ if sys.platform == "win32" and os.getenv("OPENSSL_LIBNAME") is None:
107
103
  print("No OPENSSL_LIBNAME environment variable found, updating `setup.py`!")
108
104
  patch_pysqlcipher_setup(pysqlcipher_dir, crypto_lib)
109
105
 
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.2'
16
- __version_tuple__ = version_tuple = (0, 2, 2)
15
+ __version__ = version = '0.3.0'
16
+ __version_tuple__ = version_tuple = (0, 3, 0)
pyrekordbox/anlz/file.py CHANGED
@@ -114,10 +114,10 @@ class AnlzFile(abc.Mapping):
114
114
  # Check if the file is garbled (only on exported files)
115
115
  # For this we check the validity of mood and bank
116
116
  # Mood: High=1, Mid=2, Low=3
117
- # Bank: 1-8
117
+ # Bank: 0-8
118
118
  mood = Int16ub.parse(tag_data[18:20])
119
119
  bank = Int16ub.parse(tag_data[28:30])
120
- if 1 <= mood <= 3 and 1 <= bank <= 8:
120
+ if 1 <= mood <= 3 and 0 <= bank <= 8:
121
121
  logger.debug("PSSI is not garbled!")
122
122
  else:
123
123
  logger.debug("PSSI is garbled!")
pyrekordbox/config.py CHANGED
@@ -223,9 +223,10 @@ def read_rekordbox6_asar(rb6_install_dir: Union[str, Path]) -> str:
223
223
  location = rb6_install_dir / "rekordboxAgent-win32-x64" / "resources"
224
224
  encoding = "ANSI"
225
225
  elif sys.platform == "darwin":
226
+ if not str(rb6_install_dir).endswith(".app"):
227
+ rb6_install_dir = rb6_install_dir / "rekordbox.app"
226
228
  location = (
227
229
  rb6_install_dir
228
- / "rekordbox.app"
229
230
  / "Contents"
230
231
  / "MacOS"
231
232
  / "rekordboxAgent.app"
@@ -244,6 +245,14 @@ def read_rekordbox6_asar(rb6_install_dir: Union[str, Path]) -> str:
244
245
  return data
245
246
 
246
247
 
248
+ def _extract_version(name, major_version):
249
+ name = name.replace(".app", "") # Needed for MacOS
250
+ ver_str = name.replace("rekordbox", "").strip()
251
+ if not ver_str:
252
+ ver_str = str(major_version)
253
+ return ver_str
254
+
255
+
247
256
  def _get_rb_config(
248
257
  pioneer_install_dir: Path,
249
258
  pioneer_app_dir: Path,
@@ -272,43 +281,38 @@ def _get_rb_config(
272
281
  if application_dirname:
273
282
  # Applitcation dirname is given, only extract version from it
274
283
  # `major_version` is compared to the version string
275
- rb_version = application_dirname.replace("rekordbox", "").strip()
276
- rb_version = rb_version.replace(".app", "")
277
- if not rb_version.startswith(str(major_version)):
278
- raise ValueError(
279
- f"Major version is {major_version}, but the supplied application "
280
- f"dirname is '{application_dirname}'"
281
- )
282
284
  rb_prog_dir = pioneer_install_dir / application_dirname
283
285
  if not rb_prog_dir.exists():
284
286
  raise InvalidApplicationDirname(
285
287
  f"The supplied application dirname '{application_dirname}' does not "
286
288
  f"exist in '{pioneer_install_dir}'"
287
289
  )
290
+ rb_version = _extract_version(application_dirname, major_version)
288
291
  else:
289
292
  # Get latest Rekordbox installation directory for major release `major_version`
290
293
 
291
294
  # Find all 'V.x.x' version strings in dir names
292
- versions = list()
295
+ installations = {}
293
296
  for p in pioneer_install_dir.iterdir():
294
297
  name = p.name
295
298
  if name.startswith("rekordbox"):
296
- ver_str = name.replace("rekordbox", "").strip()
297
- ver_str = ver_str.replace(".app", "")
299
+ ver_str = _extract_version(name, major_version)
298
300
  if ver_str.startswith(str(major_version)):
299
- v = packaging.version.parse(ver_str)
300
- versions.append(v)
301
+ version = packaging.version.parse(ver_str)
302
+ installations[version] = name
301
303
  # Get latest 'V.x.x' version string and assure there is one
302
- versions.sort() # key=lambda s: list(map(int, s.split("."))))
304
+ versions = list(installations.keys())
305
+ versions.sort()
303
306
  try:
304
- rb_version = str(versions[-1])
307
+ version = versions[-1]
305
308
  except IndexError:
306
309
  raise FileNotFoundError(
307
310
  f"No Rekordbox {major_version} folder found in installation "
308
311
  f"directory '{pioneer_install_dir}'"
309
312
  )
310
313
  # Name of the Rekordbox application directory in `pioneer_install_dir`
311
- rb_prog_dir = pioneer_install_dir / f"rekordbox {rb_version}"
314
+ rb_version = str(versions[-1])
315
+ rb_prog_dir = pioneer_install_dir / installations[version]
312
316
 
313
317
  # Check installation directory
314
318
  if not rb_prog_dir.exists():