Coder Social home page Coder Social logo

dylanljones / pyrekordbox Goto Github PK

View Code? Open in Web Editor NEW
165.0 5.0 22.0 6.77 MB

Inofficial Python package for interacting with the database and other files (XML, ANLZ, MySettings) of Pioneers Rekordbox DJ software

Home Page: https://pyrekordbox.readthedocs.io/en/latest/

License: MIT License

Python 100.00%
python rekordbox music-library music database rekordbox-v5 rekordbox-v6 library dj anlz

pyrekordbox's Introduction

logo

Tests Codecov Version Python Platform license: MIT style: ruff

Disclaimer: This project is not affiliated with Pioneer Corp. or its related companies in any way and has been written independently! Pyrekordbox is licensed under the MIT license. The maintainers of the project are not liable for any damages to your Rekordbox library.

Pyrekordbox is a Python package for interacting with the library and export data of Pioneers Rekordbox DJ Software. It currently supports

  • Rekordbox v6 master.db database
  • Rekordbox XML database
  • Analysis files (ANLZ)
  • My-Setting files

Tested Rekordbox versions: 5.8.6 | 6.5.3 | 6.7.7

โš ๏ธ This project is still under development and might contain bugs or have breaking API changes in the future. Check the changelog for recent changes!

๐Ÿ”ง Installation

Pyrekordbox is available on PyPI:

pip install pyrekordbox

Alternatively, it can be installed via GitHub

pip install git+https://github.com/dylanljones/pyrekordbox.git@VERSION

where VERSION is a release, tag or branch name.

Dependencies

Unlocking the new Rekordbox 6 master.db database file requires SQLCipher. Pyrekordbox makes no attempt to download/install SQLCipher, as it is a pure Python package - whereas the SQLCipher/sqlcipher3 installation is platform-dependent and can not be installed via pip.

Windows

SQLCipher can be used by building the libary against an amalgamation with sqlcipher3. For a detailed instruction, see the installation guide.

MacOS

For MacOS follow these steps:

  1. Install Homebrew if you do not have it on your machine.
  2. Install SQLCipher with brew install SQLCipher.
  3. With the python environment you are using to run pyrekordbox active execute the following:
git clone https://github.com/coleifer/sqlcipher3
cd sqlcipher3
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
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

Make sure the C_INCLUDE and LIBRARY_PATH point to the installed SQLCipher path. It may differ on your machine. If you are having issues installing sqlcipher3 on M1 Macs please refer to the installation guide.

๐Ÿš€ Quick-Start

Read the full documentation on ReadTheDocs!

โ— Please make sure to back up your Rekordbox collection before making changes with pyrekordbox or developing/testing new features. The backup dialog can be found under "File" > "Library" > "Backup Library"

Configuration

Pyrekordbox looks for installed Rekordbox versions and sets up the configuration automatically. The configuration can be checked by calling:

from pyrekordbox import show_config

show_config()

If for some reason the configuration fails the values can be updated by providing the paths to the directory where Pioneer applications are installed (pioneer_install_dir) and to the directory where Pioneer stores the application data (pioneer_app_dir)

from pyrekordbox.config import update_config

update_config("<pioneer_install_dir>", "<pioneer_app_dir>")

Alternatively the two paths can be specified in a configuration file under the section rekordbox. Supported configuration files are pyproject.toml, setup.cfg, pyrekordbox.toml, pyrekordbox.cfg and pyrekordbox.yaml.

Rekordbox 6 database

Rekordbox 6 now uses a SQLite database for storing the collection content. Unfortunatly, the new master.db SQLite database is encrypted using SQLCipher, which means it can't be used without the encryption key. However, since your data is stored and used locally, the key must be present on the machine running Rekordbox.

Pyrekordbox can unlock the new Rekordbox master.db SQLite database and provides an easy interface for accessing the data stored in it:

from pyrekordbox import Rekordbox6Database

db = Rekordbox6Database()

for content in db.get_content():
    print(content.Title, content.Artist.Name)

playlist = db.get_playlist()[0]
for song in playlist.Songs:
    content = song.Content
    print(content.Title, content.Artist.Name)

Fields in the Rekordbox database that are stored without linking to other tables can be changed via the corresponding property of the object:

content = db.get_content()[0]
content.Title = "New Title"

Some fields are stored as references to other tables, for example the artist of a track. Check the documentation of the corresponding object for more information. So far only a few tables support adding or deleting entries:

  • DjmdPlaylist: Playlists/Playlist Folders
  • DjmdSongPlaylist: Songs in a playlist
  • DjmdAlbum: Albums
  • DjmdArtist: Artists
  • DjmdGenre: Genres
  • DjmdLabel: Labels

If the automatic key extraction fails the command line interface of pyrekordbox provides a command for downloading the key from known sources and writing it to the cache file:

python -m pyrekordbox download-key

Once the key is cached the database can be opened without providing the key. The key can also be provided manually:

db = Rekordbox6Database(key="<insert key here>")

Rekordbox XML

The Rekordbox XML database is used for importing (and exporting) Rekordbox collections including track metadata and playlists. They can also be used to share playlists between two databases.

Pyrekordbox can read and write Rekordbox XML databases.

from pyrekordbox.rbxml import RekordboxXml

xml = RekordboxXml("database.xml")

track = xml.get_track(0)    # Get track by index (or TrackID)
track_id = track.TrackID    # Access via attribute
name = track["Name"]        # or dictionary syntax

path = "/path/to/file.mp3"
track = xml.add_track(path) # Add new track
track["Name"] = "Title"     # Add attributes to new track
track["TrackID"] = 10       # Types are handled automatically

# Get playlist (folder) by path
pl = xml.get_playlist("Folder", "Sub Playlist")
keys = pl.get_tracks()  # Get keys of tracks in playlist
ktype = pl.key_type     # Key can either be TrackID or Location

# Add tracks and sub-playlists (folders)
pl.add_track(track.TrackID)
pl.add_playlist("Sub Sub Playlist")

Rekordbox ANLZ files

Rekordbox stores analysis information of the tracks in the collection in specific files, which also get exported to decives used by Pioneer professional DJ equipment. The files have names like ANLZ0000 and come with the extensions .DAT, .EXT or .2EX. They include waveforms, beat grids (information about the precise time at which each beat occurs), time indices to allow efficient seeking to specific positions inside variable bit-rate audio streams, and lists of memory cues and loop points.

Pyrekordbox can parse all three analysis files, although not all the information of the tracks can be extracted yet.

from pyrekordbox.anlz import AnlzFile

anlz = AnlzFile.parse_file("ANLZ0000.DAT")
beat_grid = anlz.get("beat_grid")
path_tags = anlz.getall_tags("path")

Changing and creating the Rekordbox analysis files is planned as well, but for that the full structure of the analysis files has to be understood.

Unsupported ANLZ tags:

  • PCOB
  • PCO2
  • PSSI
  • PWV6
  • PWV7
  • PWVC

Rekordbox My-Settings

Rekordbox stores the user settings in *SETTING.DAT files, which get exported to USB devices. These files are either in the PIONEERdirectory of a USB drive (device exports), but are also present for on local installations of Rekordbox 6. The setting files store the settings found on the "DJ System" > "My Settings" page of the Rekordbox preferences. These include language, LCD brightness, tempo fader range, crossfader curve and other settings for Pioneer professional DJ equipment.

Pyrekordbox supports both parsing and writing My-Setting files.

from pyrekordbox.mysettings import read_mysetting_file

mysett = read_mysetting_file("MYSETTINGS.DAT")
sync = mysett.get("sync")
quant = mysett.get("quantize")

The DEVSETTING.DAT file is still not supported

๐Ÿ’ก File formats

A summary of the Rekordbox file formats can be found in the documentation:

๐Ÿ’ป Development

If you encounter an issue or want to contribute to pyrekordbox, please feel free to get in touch, open an issue or create a new pull request! A guide for contributing to pyrekordbox and the commit-message style can be found in CONTRIBUTING.

For general questions or discussions about Rekordbox, please use GitHub Discussions instead of opening an issue.

Pyrekordbox is tested on Windows and MacOS, however some features can't be tested in the CI setup since it requires a working Rekordbox installation.

๐Ÿ”— Related Projects and References

pyrekordbox's People

Contributors

cvdub avatar dylanljones avatar gsuberland avatar joj0 avatar jvacek avatar lgtm-migrator avatar phil-bergmann avatar pre-commit-ci[bot] avatar troyhacks avatar voigtjr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

pyrekordbox's Issues

Support for renamed rekordbox.app

I rename my rekordbox app packages with additional version numbers, thus I can revert to previous versions if something gets unstable with an update (yes it happens to Pioneer software as well ;-)

eg. /Applications/rekordbox 6/rekordbox 6.7.3.app

I'm not sure if this is supported yet, I'm getting:

Traceback (most recent call last):
  File "/Users/jojo/.pyenv/versions/jteezer310/bin/jteezer", line 8, in <module>
    sys.exit(root())
  File "/Users/jojo/.pyenv/versions/3.10.5/envs/jteezer310/lib/python3.10/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/Users/jojo/.pyenv/versions/3.10.5/envs/jteezer310/lib/python3.10/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/Users/jojo/.pyenv/versions/3.10.5/envs/jteezer310/lib/python3.10/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/jojo/.pyenv/versions/3.10.5/envs/jteezer310/lib/python3.10/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/jojo/.pyenv/versions/3.10.5/envs/jteezer310/lib/python3.10/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/Users/jojo/.pyenv/versions/3.10.5/envs/jteezer310/lib/python3.10/site-packages/click/decorators.py", line 38, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/Users/jojo/git/jteezer/jteezer/jteezer.py", line 580, in rekordbox_cmd
    update_config(pioneer_install_dir, pioneer_app_dir)
  File "/Users/jojo/.pyenv/versions/3.10.5/envs/jteezer310/lib/python3.10/site-packages/pyrekordbox/config.py", line 189, in update_config
    conf = _get_rb6_config(pioneer_install_dir, pioneer_app_dir)
  File "/Users/jojo/.pyenv/versions/3.10.5/envs/jteezer310/lib/python3.10/site-packages/pyrekordbox/config.py", line 108, in _get_rb6_config
    conf = _get_rb_config(pioneer_prog_dir, pioneer_app_dir, version=6)
  File "/Users/jojo/.pyenv/versions/3.10.5/envs/jteezer310/lib/python3.10/site-packages/pyrekordbox/config.py", line 49, in _get_rb_config
    versions.sort(key=lambda s: list(map(int, s.split("."))))
  File "/Users/jojo/.pyenv/versions/3.10.5/envs/jteezer310/lib/python3.10/site-packages/pyrekordbox/config.py", line 49, in <lambda>
    versions.sort(key=lambda s: list(map(int, s.split("."))))
ValueError: invalid literal for int() with base 10: 'app'

while doing

    from pyrekordbox import show_config
    from pyrekordbox.config import update_config

    pioneer_install_dir = "/Applications/rekordbox 6"
    pioneer_app_dir = "/Users/jojo/Library/Application Support/Pioneer/rekordbox6"

    update_config(pioneer_install_dir, pioneer_app_dir)
    show_config()

error with stockDate in smart lists

There is the following error:
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with DjmdContent.StockDate has an attribute 'month'

when smart list has property: SmartList:

I expect a work aroudn might be need to get this working.

Thanks

Passing in incorrect key to Rekordbox6Database class does not raise exception

Describe the bug
If you pass in the incorrect key into the Rekordbox6Database class, the init function still runs through and tries to run some SQL commands.

To Reproduce
Steps to reproduce the behavior:

db = Rekordbox6Database(key='iamthewrongkey')

The resulting error is:

Traceback (most recent call last):
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
    self.dialect.do_execute(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
    cursor.execute(statement, parameters)
sqlcipher3.dbapi2.DatabaseError: file is not a database

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/benhearn/Documents/traxmanager_dev/TraxManager/traxmanager/utils/rekordbox_utils.py", line 180, in <module>
    change_file_name(move_to, 'Sweely - Nice or Ugly (Test Name Change Mix)')
  File "/Users/benhearn/Documents/traxmanager_dev/TraxManager/traxmanager/utils/rekordbox_utils.py", line 53, in inner
    content_obj = get_folder_path_content_object(db, orig_path)
  File "/Users/benhearn/Documents/traxmanager_dev/TraxManager/traxmanager/utils/rekordbox_utils.py", line 121, in get_folder_path_content_object
    success = content_obj.all()
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/orm/query.py", line 2693, in all
    return self._iter().all()  # type: ignore
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/orm/query.py", line 2847, in _iter
    result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/orm/session.py", line 2308, in execute
    return self._execute_internal(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/orm/session.py", line 2190, in _execute_internal
    result: Result[Any] = compile_state_cls.orm_execute_statement(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement
    result = conn.execute(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
    return meth(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
    return connection._execute_clauseelement(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
    ret = self._execute_context(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
    return self._exec_single_context(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
    self._handle_dbapi_exception(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
    self.dialect.do_execute(
  File "/Users/benhearn/miniconda3/envs/traxmanager_dev_venv/lib/python3.9/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.DatabaseError: (sqlcipher3.dbapi2.DatabaseError) file is not a database
[SQL: SELECT "djmdContent"."ID" AS "djmdContent_ID", "djmdContent"."FolderPath" AS "djmdContent_FolderPath", "djmdContent"."FileNameL" AS "djmdContent_FileNameL", "djmdContent"."FileNameS" AS "djmdContent_FileNameS", "djmdContent"."Title" AS "djmdContent_Title", "djmdContent"."ArtistID" AS "djmdContent_ArtistID", "djmdContent"."AlbumID" AS "djmdContent_AlbumID", "djmdContent"."GenreID" AS "djmdContent_GenreID", "djmdContent"."BPM" AS "djmdContent_BPM", "djmdContent"."Length" AS "djmdContent_Length", "djmdContent"."TrackNo" AS "djmdContent_TrackNo", "djmdContent"."BitRate" AS "djmdContent_BitRate", "djmdContent"."BitDepth" AS "djmdContent_BitDepth", "djmdContent"."Commnt" AS "djmdContent_Commnt", "djmdContent"."FileType" AS "djmdContent_FileType", "djmdContent"."Rating" AS "djmdContent_Rating", "djmdContent"."ReleaseYear" AS "djmdContent_ReleaseYear", "djmdContent"."RemixerID" AS "djmdContent_RemixerID", "djmdContent"."LabelID" AS "djmdContent_LabelID", "djmdContent"."OrgArtistID" AS "djmdContent_OrgArtistID", "djmdContent"."KeyID" AS "djmdContent_KeyID", "djmdContent"."StockDate" AS "djmdContent_StockDate", "djmdContent"."ColorID" AS "djmdContent_ColorID", "djmdContent"."DJPlayCount" AS "djmdContent_DJPlayCount", "djmdContent"."ImagePath" AS "djmdContent_ImagePath", "djmdContent"."MasterDBID" AS "djmdContent_MasterDBID", "djmdContent"."MasterSongID" AS "djmdContent_MasterSongID", "djmdContent"."AnalysisDataPath" AS "djmdContent_AnalysisDataPath", "djmdContent"."SearchStr" AS "djmdContent_SearchStr", "djmdContent"."FileSize" AS "djmdContent_FileSize", "djmdContent"."DiscNo" AS "djmdContent_DiscNo", "djmdContent"."ComposerID" AS "djmdContent_ComposerID", "djmdContent"."Subtitle" AS "djmdContent_Subtitle", "djmdContent"."SampleRate" AS "djmdContent_SampleRate", "djmdContent"."DisableQuantize" AS "djmdContent_DisableQuantize", "djmdContent"."Analysed" AS "djmdContent_Analysed", "djmdContent"."ReleaseDate" AS "djmdContent_ReleaseDate", "djmdContent"."DateCreated" AS "djmdContent_DateCreated", "djmdContent"."ContentLink" AS "djmdContent_ContentLink", "djmdContent"."Tag" AS "djmdContent_Tag", "djmdContent"."ModifiedByRBM" AS "djmdContent_ModifiedByRBM", "djmdContent"."HotCueAutoLoad" AS "djmdContent_HotCueAutoLoad", "djmdContent"."DeliveryControl" AS "djmdContent_DeliveryControl", "djmdContent"."DeliveryComment" AS "djmdContent_DeliveryComment", "djmdContent"."CueUpdated" AS "djmdContent_CueUpdated", "djmdContent"."AnalysisUpdated" AS "djmdContent_AnalysisUpdated", "djmdContent"."TrackInfoUpdated" AS "djmdContent_TrackInfoUpdated", "djmdContent"."Lyricist" AS "djmdContent_Lyricist", "djmdContent"."ISRC" AS "djmdContent_ISRC", "djmdContent"."SamplerTrackInfo" AS "djmdContent_SamplerTrackInfo", "djmdContent"."SamplerPlayOffset" AS "djmdContent_SamplerPlayOffset", "djmdContent"."SamplerGain" AS "djmdContent_SamplerGain", "djmdContent"."VideoAssociate" AS "djmdContent_VideoAssociate", "djmdContent"."LyricStatus" AS "djmdContent_LyricStatus", "djmdContent"."ServiceID" AS "djmdContent_ServiceID", "djmdContent"."OrgFolderPath" AS "djmdContent_OrgFolderPath", "djmdContent"."Reserved1" AS "djmdContent_Reserved1", "djmdContent"."Reserved2" AS "djmdContent_Reserved2", "djmdContent"."Reserved3" AS "djmdContent_Reserved3", "djmdContent"."Reserved4" AS "djmdContent_Reserved4", "djmdContent"."ExtInfo" AS "djmdContent_ExtInfo", "djmdContent".rb_file_id AS "djmdContent_rb_file_id", "djmdContent"."DeviceID" AS "djmdContent_DeviceID", "djmdContent"."rb_LocalFolderPath" AS "djmdContent_rb_LocalFolderPath", "djmdContent"."SrcID" AS "djmdContent_SrcID", "djmdContent"."SrcTitle" AS "djmdContent_SrcTitle", "djmdContent"."SrcArtistName" AS "djmdContent_SrcArtistName", "djmdContent"."SrcAlbumName" AS "djmdContent_SrcAlbumName", "djmdContent"."SrcLength" AS "djmdContent_SrcLength", "djmdContent"."UUID" AS "djmdContent_UUID", "djmdContent".rb_data_status AS "djmdContent_rb_data_status", "djmdContent".rb_local_data_status AS "djmdContent_rb_local_data_status", "djmdContent".rb_local_deleted AS "djmdContent_rb_local_deleted", "djmdContent".rb_local_synced AS "djmdContent_rb_local_synced", "djmdContent".usn AS "djmdContent_usn", "djmdContent".rb_local_usn AS "djmdContent_rb_local_usn", "djmdContent".created_at AS "djmdContent_created_at", "djmdContent".updated_at AS "djmdContent_updated_at" 
FROM "djmdContent" 
WHERE "djmdContent"."FolderPath" = ?]
[parameters: ('/Users/benhearn/Music/test_folder copy/_backup_test_folder/test_folder/test_demo_02/Sweely - Nice Or Ugly.wav',)]
(Background on this error at: https://sqlalche.me/e/20/4xp6)

Expected behavior
Expected an exception to be raised

Environment

  • Pyrekordbox version:
    Latest

Deriving gain in dB from gain and peak mixer parameters

I figured out the format for mixer gain parameters in djmdMixerParam. The High and Low values are the upper and lower halves of a packed 32-bit IEEE754 float value. Each value represents a linear voltage gain factor, i.e. 1 = 0dB, 10 = +20dB, 0.1 = -20dB.

Here's a quick script to convert the values:

from pyrekordbox import Rekordbox6Database
import math
import struct

db = Rekordbox6Database()
for t in db.get_mixer_param():
	gain_factor = struct.unpack('!f', ((t.GainHigh << 16) | t.GainLow).to_bytes(4,byteorder='big'))[0]
	gain_dB = 20.0 * math.log10(gain_factor)
	peak_factor = struct.unpack('!f', ((t.PeakHigh << 16) | t.PeakLow).to_bytes(4,byteorder='big'))[0]
	peak_dB = 20.0 * math.log10(peak_factor)
	print("\"" + t.Content.Artist.Name + " - " + t.Content.Title + "\", gain=" + str(round(gain_dB,2)) + "dB, peak=" + str(round(peak_dB,2)) + "dB")

Example output:

"Phaction - Echo", gain=7.18dB, peak=-6.49dB
"Netsky - Come Alive", gain=4.32dB, peak=-7.17dB
"Netsky - Escape", gain=3.47dB, peak=-5.6dB
"Netsky - Everyday", gain=4.34dB, peak=-8.26dB
"Netsky - Eyes Closed", gain=3.32dB, peak=-5.22dB
"Netsky - I Refuse", gain=3.74dB, peak=-5.61dB
"Netsky - Iron Heart", gain=4.41dB, peak=-5.52dB
"Netsky - Let's Leave Tomorrow", gain=6.26dB, peak=-3.62dB
"Netsky - Secret Agent", gain=4.95dB, peak=-3.32dB
"Netsky - Starlight", gain=4.15dB, peak=-5.65dB

The gain value is the value of the auto-gain knob in the Grid Edit panel, which is also set by the analysis process. The peak value doesn't seem to be actually exposed in the UI, but it's the peak amplitude of the track (I've verified that this matches the peak amplitude in Tenacity) and is updated when the waveform is loaded.

You can convert the dB numbers back to linear gain factor by calculating pow(10.0, g/20.0), then re-packing the float to a pair of 16-bit unsigned integers using the same struct.unpack approach.

Would be cool to get this conversion in the API, either as a property or a function on the DjmdMixerParam class.

Question: How to access Models?

Hi @dylanljones,

How would I access the models (DjmdHistory) to do something like db.get_history().order_by(DjmdHistory.DateCreated.desc())?

Currently, I initialize the models on my own using your template from tables.py.

I could not get it working by from pyrekordbox import Rekordbox6Database, DjmdHistory

PSSI tag beat field not being parsed correctly

Describe the bug

Inside each parsed PSSI struct, the SongStructureEntry's beat field is not parsed properly. The numbers are far too large. I am not sure if this is an endianness issue, or an offset problem.

I can share the .EXT file if you like as well if you'd like to reproduce.

To Reproduce

Steps to reproduce the behavior:

import os
from pyrekordbox.anlz import AnlzFile

path = os.path.expanduser("~/Desktop/ANLZ0000.EXT")
anlz = AnlzFile.parse_file(path)
for song_structure_struct in anlz.get('PSSI').entries:
    print(song_structure_struct.beat)

The resulting output is:

62718
63756
63933
60541
65349
1305
62932
62903
60767
61776
56381

Expected behavior
The output should be much smaller numbers, and of course, they would also have to be monotonically increasing. In this case, I checked the exact numbers with the online parser tool, and they should be:

1
9
73
137
169
233
265
329
393
425
457

and here's a screenshot of the first two entries:

Screenshot 2023-12-10 at 11 43 02 PM

I can also verify by checking in rekordbox itself, and indeed, the first two entries should start at 1 and 9:

Screenshot 2023-12-10 at 11 42 45 PM

so clearly we're either not: 1) parsing the right binary chunk of data, or 2) not using the correct interpretation of that chunk (ie: endianness, type, etc). what do you think it could be? I'm more than happy to help dig in, but just wanted to document this in case you had some suggestions.

Environment

  • Pyrekordbox version: master (2f13a93c9d22b709902b05394b64667491d7dbd0)
  • Rekordbox version: 6.6.9

Alternative method for capturing database key

Here's a backup method for getting the database key, which might be worth documenting:

  1. Download x64dbg and run it.
  2. Options -> Preferences. Make sure "Entry Breakpoint" is set in the Events tab.
  3. File -> Open... rekordbox.exe (the main application executable)
  4. Look at the status bar. It should have a yellow "Paused" icon followed by some status text. Right now it should say "System breakpoint reached!"
  5. Hit F9 or press the Run button in the top bar. The status text should change to "INT3 breakpoint 'entry breakpoint' at <rekordbox.EntryPoint>".
  6. Click in the disassembly window, then press Ctrl+G to open the Go To Expression box, and search for sqlite3_key_v2 and press OK. This should jump you to the code for that function, which typically starts with mov dword ptr ss:[rsp+xx], r9d or similar.
  7. Without clicking anywhere on the disassembly window, press F2 to toggle breakpoint. The top instruction's address should turn red.
  8. Hit F9 or press the Run button in the top bar. The status text will start changing a bunch, while the program starts up. Wait until the status bar goes back to "Paused" in yellow. If the status text says something like "First chance exception on..." press F9 again.
  9. The status bar should go to "Paused" in yellow again, this time with status text that says "INT3 breakpoint at <sqlite3.sqlite3_key_v2>". This means our breakpoint has been hit.
  10. Click the register panel (top right, where RAX, RBX, RCX, etc. are listed) so it updates. Right click the red address next to R8, and click "Follow in dump".
  11. The dump at the bottom left will move to that address. Right click the dump panel and select Text -> ASCII at the bottom. You should now see the key as a string. You can drag-select it, then right click to copy selected line.
  12. Go to Debug -> Close to close the process, then close x64dbg.

image

This is pretty much guaranteed to work regardless of what they do with future upgrades, since you're breakpointing the function that sets the key when the database opens, and the API interface is set by the makers of sqlcipher so it won't change.

Update to the ``app.asar`` file in Rekordbox v6.6.5 breaks database unlocking

Pioneer changed the app.asar file contents in Rekordbox version 6.6.5.
The encryption password of the database key is no longer stored in plain text, which breaks the database unlocking.

Previously, the app.asar file contained JS files in plain text.
Since update 6.6.5 the JS files are now stored in a compiled format (.jsc).
The password should still be somewhere in the content of the file (jsc/controllers/auth_manager.jsc), but it can no longer be extracted easily.

Please feel free to join the discussion if you have any ideas!

Environment

  • Rekordbox version: >=6.6.5

Update filename example shows modified path

Shouldn't OSC_SAMPLER/ remain in the path if all you're doing is updating the name?

Updating the file name changes the database entry

        >>> db = Rekordbox6Database()
        >>> cont = db.get_content()[0]
        >>> cont.FolderPath
        C:/Music/PioneerDJ/Sampler/OSC_SAMPLER/PRESET ONESHOT/NOISE.wav

        >>> new_name = "noise"
        >>> db.update_content_filename(cont, new_name)
        >>> cont.FolderPath
        C:/Music/PioneerDJ/Sampler/PRESET ONESHOT/noise.wav

I assume it was just copy/pasted incorrectly from the update path function.

C:/Music/PioneerDJ/Sampler/PRESET ONESHOT/noise.wav

device library plus key

Hey, i have the new device library plus key, it's static, if anyone is interested I can post it here.

ANLZ file paths don't exist

Describe the bug
I was able to unlock and read the RB6 database. However, upon inspection, the analysis paths returned do not exist.

To Reproduce
Steps to reproduce the behavior:

import os
import json

from pyrekordbox import show_config, Rekordbox6Database
from pyrekordbox.anlz import AnlzFile

show_config()

with open('rekordbox.json', 'r') as f:
    secrets = json.load(f)

db = Rekordbox6Database(key=secrets["key"], unlock=True)

analysis_path = None
for content in db.get_content():
    print(f"Analysis file for track: {content.Title} is located at: {content.AnalysisDataPath}")
    analysis_path = content.AnalysisDataPath
    break

# try to find the analysis file
path = os.path.join(db.share_directory, analysis_path.replace('/PIONEER', 'PIONEER'))
anlz = AnlzFile.parse_file(path)

The resulting error is:

Pioneer:
   app_dir =      /Users/me/Library/Application Support/Pioneer
   install_dir =  /Applications
Rekordbox 5:
Rekordbox 6:
   app_dir =      /Users/me/Library/Application Support/Pioneer/rekordbox6
   db_dir =       /Users/me/Library/Pioneer/rekordbox
   db_path =      /Users/me/Library/Pioneer/rekordbox/master.db
   install_dir =  /Applications/rekordbox 6
   version =      6

Analysis file for track: NOISE is located at: /PIONEER/USBANLZ/553/e766b-0928-483a-a2fc-b8a7ef4dfa79/ANLZ0000.DAT
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[15], line 24
     22 # try to find the analysis file
     23 path = os.path.join(db.share_directory, analysis_path.replace('/PIONEER', 'PIONEER'))
---> 24 anlz = AnlzFile.parse_file(path)

File ~/code/keydetection/lib/pyrekordbox/pyrekordbox/anlz/file.py:89, in AnlzFile.parse_file(cls, path)
     86     raise ValueError(f"File type '{ext}' not supported!")
     88 logger.debug(f"Reading file {path.name}")
---> 89 with open(path, "rb") as fh:
     90     data = fh.read()
     92 self = cls.parse(data)

FileNotFoundError: [Errno 2] No such file or directory: '/Users/me/Library/Pioneer/rekordbox/share/PIONEER/USBANLZ/553/e766b-0928-483a-a2fc-b8a7ef4dfa79/ANLZ0000.DAT'

Expected behavior

ls /Users/me/Library/Pioneer/rekordbox/share/PIONEER/USBANLZ/553/e766b-0928-483a-a2fc-b8a7ef4dfa79/ANLZ0000.DAT

should report this is an actual file.

Environment

  • Pyrekordbox version: master (2f13a93c9d22b709902b05394b64667491d7dbd0)
  • Rekordbox version: 6.6.9

Perhaps I'm not using the package correctly? But this does seem like an issue. I'd be happy to help track it down if you have any suggestions!

Root logger set as NOSET in __init__.py causing trouble with user-defined logging solutions or other libraries

Describe the bug
init.py file contains root logging setting, which sets it to NOSET, which causes issues with other libraries where logging level is not set but logger is used

To Reproduce
Steps to reproduce the behavior:
Use in combination in project with library like spotipy

The resulting error is:
Unexpected logs and no control over logging overall

Expected behavior
Library should not change the logging level of the logging root

Changing content data (file path & file name) need to change other columns as well

Describe the bug
When changing the "FilePath" column in the "update_content_path" function, the code should also be changing the "OrgFolderPath" coilumn if the column has a value and if it matches with the FilePath column

Inside the "update_content_filename" function, you are only changing the FilePath column by calling the aforementioned function and should also be changing the FileNameL column.

Functions present at lines: 1825 & 1900 of the database.py file

Great work all around!

Switching from pysqlchipher3 to sqlcipher3

Hey everyone,

I am currently working on the migration to SqlAlchemy 2.0. It seems like the currently used pysqlchipher3 package is using an too old version of SQLite. I found a more up-to-date implementation: sqlcipher3. This slightly changes the installation steps on Windows, which I am updating now. Can anyone confirm if this package also works on MacOS? Do the install instructions (except the GitHub URL) have to be changed?

git clone https://github.com/coleifer/sqlcipher3
cd sqlcipher3
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
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

Would really appreciate some help on updating the installation docs!

How to overwrite the artwork of tracks in the RBv6 database?

Is your feature request related to a problem? Please describe.
I would like to overwrite the image path of tracks.

Describe the solution you'd like
A method to overwrite the X metadata of an Y song by it's id

Additional context
I only found that it's possible to read data but not write on

Import tracks from XML in to rekordbox 5 collection

First of all, thanks a lot for this package! This is not a feature request for pyrekordbox but just wanted to ask if anyone knew of a way to achive the following.

I've created an app that uses this package to make a rekordbox xml from tracks on disk. I then import the created rekordbox xml in to rekordbox. I'd then like to import the tracks and playlist structure from the XML in to rekordbox's collection. As far as I know, rekordbox only has a export to xml option, not an import from xml one. Any idea how I can do this?

In the attached image I want to import the highlighted 'rekordbox xml' in to the collection.

image

Support for Rekordbox 7

Is your feature request related to a problem? Please describe.
Rekordbox 7 has been recently released. For this library to be up-to-date, rekordbox 7 support is required

Describe the solution you'd like
Please add support for rekordbox 7

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.