(1) `rekordbox` package
Why?
Creating a package the distinguishes Rekordbox operations from platform-
agnostic operations encourages development that focuses on each category.
What?
Relocate modules that operate on Rekordbox's XML database files
(`utils.randomize_tracks` and `utils.generate_genre_playlists`) to a
`rekordbox` package.
Proposal:
In order to port the functionality of the `rekordbox` package for, e.g., Serato
users, a new package called `serializers` should be created which is
responsible for converting the databases of various DJ software tools into an
XML format that allows the existing features of the `rekordbox` package to be
reused.
(2) `MyTagParser` playlist builder
Why?
Automatically generating playlists from Rekordbox's "My Tag" system makes
these tags portable so they can be accessed on CDJs, other users' laptops,
or even non-Pioneer systems via the `utils.copy_playlists_tracks` module. In
addition, parsing "My Tag" data allows these tags to be used with the
`Combiner`.
What?
Specifies an implementation of the `TagParser` abstraction which uses
regular expressions to parse out the "My Tag" data written to the Comments
field.
NOTE: "My Tag" data is only written to the Comments field if
`Preferences > Advanced > Browse > Add "My Tag" to the "Comments"` is
enabled.
Including the key `MyTagParser` in `configs/rekordbox_playlists.json` allows
users to specify a desired playlist folder structure for "My Tag" data. The
supported feature set is the same as with the `generate_genre_playlists`
module which has been rebranded as the `GenreTagParser`.
The `MyTagParser` returns a list of tags which the calling class,
`rekordbox.rekordbox_playlist_builder.PlaylistBuilder` uses to assemble a
tag -> track ID lookup data structure which is later used to generate
Rekordbox playlists.
(3) `GenreTagParser` playlist builder
Why?
To be consistent with the design of the `TagParser` abstraction in the
`rekordbox.tag_parsers` module, used by the
`rekordbox.rekordbox_playlist_builder` module, the
`generate_genre_playlists` module was refactored.
What?
Specifies an implementation of the `TagParser` abstraction which splits the
Genre field on `/` forward slash characters.
Including the key `GenreTagParser` in `configs/rekordbox_playlists.json`
allows users to specify a desired playlist folder structure for Genre data. The
supported features set is the same as with the `generate_genre_playlists`
module.
The `GenreTagParser` returns a list of tags which the calling class,
`rekordbox.rekordbox_playlist_builder.PlaylistBuilder` uses to assemble a
tag -> track ID lookup data structure which is later used to generate
Rekordbox playlists.
NOTE: the former options `GENERATE_GENRE_PLAYLISTS` and
`GENERATE_GENRE_PLAYLISTS_REMAINDER` have been renamed
`REKORDBOX_PLAYLISTS` and `REKORDBOX_PLAYLISTS_REMAINDER`
respectively. These options now apply to all `TagParser` implementations.
The `GENERATE_GENRE_PLAYLISTS_PURE` option has been renamed to
`GENRE_PLAYLISTS_PURE`. Additionally, the option
`GENRE_TAG_DELIMITER` has been removed and the delimiter `/` is assumed
to be consistent with the in-built "My Tag" delimiter.
(4) `Combiner` playlist builder
Why?
Rekordbox's "Track Filter" system is very limited. The `Combiner` greatly
improves upon the "Track Filter" in the following ways:
(a) Portability:
The "Track Filter" can only be used on a laptop running Rekordbox and,
furthermore, can only be used with the Master Database USB meaning that
using your USB as a Device on someone else's laptop bars you from
accessing this feature. The `Combiner` generates ordinary Rekordbox
playlists which can be exported to a Device. You can even combine these
generated playlists with the `utils.copy_playlists_tracks` module to enable
easier access to tracks on non-Pioneer systems giving users maximum
portability.
(b) Operands:
The "Track Filter" only operates on "My Tag" data. The `Combiner` can be
extended to operate on any data that can be parsed from a Rekordbox XML.
Currently supported operands are:
- Genre tags parsed with the `GenreTagParser`, e.g. `Liquid Funk`
- "My Tag" tags parsed with the `MyTagParser`, e.g. `Peak Time`
- Playlist selectors, e.g. `{My Favorite Tracks}`
- Rating selectors (0 <= numbers <= 5), e.g. `[0, 2-5]`
- BPMs selectors (numbers > 5), e.g. `[80-88, 140, 150-174]`
(c) Operators:
The "Track Filter" only supports `AND` and `OR` logic. The `Combiner`
adds the `NOT` operator into the mix which allows users to subtract tracks
belonging to the successive operand from the resulting playlists.
(d) Managed complexity:
The "Track Filter" only supports filtering a maximum of three tags using two
logic operators. The `Combiner` allows users to write boolean algebra
expressions with an arbitrary number of operands / operators to create
incredibly rich filtering logic. Subexpressions are enclosed in parentheses to
give users clear control over the order of operations in their playlist building
logic.
What?
Including the key `Combiner` in `configs/rekordbox_playlists.json` allows
users to specify a desired set of `Combiner` playlists. Unlike the other playlist
builders based on `TagParser` implementations, the `Combiner` does not
support nested folders, the "Other" playlist / folder, or the "_ignore" key.
Instead, users must specify "playlists" as a flat list of playlist names which
are, themselves, the boolean algebra expressions they wish to be evaluated to
generate the desired playlists.
The `rekordbox.rekordbox_playlist_builder.PlaylistBuilder` class manages the
overall construction of Rekordbox playlists by utilizing the various `TagParser`
implementations to extract tag information from a Rekordbox XML. The way
this class handles the `Combiner` is a bit different. It first instantiates the
`Combiner`, employs the set of `TagParser` instances, and then applies the
`Combiner` on the reduced data structure generated by the `TagParser`
instances. When the `Combiner` is initialized, it checks if any of the
`Combiner` playlists contain selectors (playlist, rating, and BPM) and, if so,
performs a pre-scan of the tracks to initialize the data structure with the
proper track IDs treating each selector as a tag for lookup.
Once the tag -> track ID lookup is completely assembled, each `Combiner`
playlist has its name evaluated as a boolean logic parse tree. Parentheses
symbolize subexpressions which create new child nodes in the parse tree. The
operators `&`, `|`, `~` (i.e. `AND`, `OR`, and `NOT`, respectively) are
appended to a list of operators belonging to a node. All other stripped values
between operators / parentheses are appended as tags to be looked up in the
tag -> track ID data structure. When a (sub)expression is terminated by a
closing parenthesis, or by reaching the end of the playlist name, the
(sub)expression is evaluated by dequeuing tags and operators from the
respective lists belonging to that node. Sets of track IDs evaluated in
subexpressions are propagated upward towards the root of the parse tree
until a final set of track IDs is returned representing the fully evaluated
`Combiner` playlist.
(5) Removed `utils.get_genres` module and the related `config.json` options
Why?
Getting genre information from the ID3 tag fields of MP3s is not useful as that
information already exists in the Rekordbox XML.
What?
Remove the `utils.get_genres` module and related `config.json` options.
(6) Add `utils.copy_playlists_tracks` module
Why?
Users may need to create an additional USB containing just the tracks from
one or more playlists. For example, one may need to create a backup for an
event, provide a USB with tracks to another DJ for a back-to-back, etc.
What?
Searches `XML_PATH` for each playlist name in `COPY_PLAYLISTS_TRACKS`
(NOTE: if playlist names are not unique, the first playlist found will be used).
Audio files stored at the tracks' `Location` field are copied to
`COPY_PLAYLISTS_TRACKS_DESTINATION`. A new XML file called
`relocated_<NAME>`, where `<NAME>` is the basename of `XML_PATH`, is
generated containing only these copied tracks with updated `Location` fields
that point to `COPY_PLAYLISTS_TRACKS_DESTINATION`.
New `config.json` options: `COPY_PLAYLISTS_TRACKS` and
`COPY_PLAYLISTS_TRACKS_DESTINATION`.
(7) Add parameterized download location
Why?
Users should not need to have their `USB_PATH` existing to use the
`utils.youtube_dl` module.
What?
Adds the option `YOUTUBE_DL_LOCATION` to `configs/config.json`. If left
empty, files will be downloaded to the current directory.
(8) Add unit tests and `pytest-cov` Action
Why?
Unit testing is obviously a good thing. As other users begin contributing, unit
testing becomes more essential. Ensuring 100% coverage on `push` and
`pull_request` events reduces / eliminates the need to manually test features
and bugfixes or manage beta release periods.
What?
Add `rekordbox.xml` test data and unit tests for 100% total coverage.
Additionally, `.github/workflows.test.yaml` has been added to trigger a
`pytest-cov` runner upon `push` and `pull_request` events.