1267 lines
42 KiB
Plaintext
1267 lines
42 KiB
Plaintext
meta:
|
|
id: rekordbox_pdb
|
|
title: DeviceSQL database export (probably generated by rekordbox)
|
|
application: rekordbox
|
|
file-extension:
|
|
- pdb
|
|
license: EPL-1.0
|
|
endian: le
|
|
|
|
doc: |
|
|
This is a relational database format designed to be efficiently used
|
|
by very low power devices (there were deployments on 16 bit devices
|
|
with 32K of RAM). Today you are most likely to encounter it within
|
|
the Pioneer Professional DJ ecosystem, because it is the format that
|
|
their rekordbox software uses to write USB and SD media which can be
|
|
mounted in DJ controllers and used to play and mix music.
|
|
|
|
It has been reverse-engineered to facilitate sophisticated
|
|
integrations with light and laser shows, videos, and other musical
|
|
instruments, by supporting deep knowledge of what is playing and
|
|
what is coming next through monitoring the network communications of
|
|
the players.
|
|
|
|
The file is divided into fixed-size blocks. The first block has a
|
|
header that establishes the block size, and lists the tables
|
|
available in the database, identifying their types and the index of
|
|
the first of the series of linked pages that make up that table.
|
|
|
|
Each table is made up of a series of rows which may be spread across
|
|
any number of pages. The pages start with a header describing the
|
|
page and linking to the next page. The rest of the page is used as a
|
|
heap: rows are scattered around it, and located using an index
|
|
structure that builds backwards from the end of the page. Each row
|
|
of a given type has a fixed size structure which links to any
|
|
variable-sized strings by their offsets within the page.
|
|
|
|
As changes are made to the table, some records may become unused,
|
|
and there may be gaps within the heap that are too small to be used
|
|
by other data. There is a bit map in the row index that identifies
|
|
which rows are actually present. Rows that are not present must be
|
|
ignored: they do not contain valid (or even necessarily well-formed)
|
|
data.
|
|
|
|
The majority of the work in reverse-engineering this format was
|
|
performed by @henrybetts and @flesniak, for which I am hugely
|
|
grateful. @GreyCat helped me learn the intricacies (and best
|
|
practices) of Kaitai far faster than I would have managed on my own.
|
|
|
|
doc-ref: https://github.com/Deep-Symmetry/crate-digger/blob/master/doc/Analysis.pdf
|
|
|
|
params:
|
|
- id: is_ext
|
|
type: bool
|
|
doc: |
|
|
Indicates whether the database schema is export or exportExt.
|
|
Set this to true when parsing an exportExt.pdb file.
|
|
|
|
seq:
|
|
- type: u4
|
|
doc: |
|
|
Unknown purpose, perhaps an unoriginal signature, seems to
|
|
always have the value 0.
|
|
- id: len_page
|
|
type: u4
|
|
doc: |
|
|
The database page size, in bytes. Pages are referred to by
|
|
index, so this size is needed to calculate their offset, and
|
|
table pages have a row index structure which is built from the
|
|
end of the page backwards, so finding that also requires this
|
|
value.
|
|
- id: num_tables
|
|
type: u4
|
|
doc: |
|
|
Determines the number of table entries that are present. Each
|
|
table is a linked list of pages containing rows of a particular
|
|
type.
|
|
- id: next_unused_page
|
|
type: u4
|
|
doc: |
|
|
@flesniak said: "Not used as any `empty_candidate`, points
|
|
past the end of the file."
|
|
- type: u4
|
|
- id: sequence
|
|
type: u4
|
|
doc: |
|
|
Sequence number incremented during every edit of the database.
|
|
- id: gap
|
|
contents: [0, 0, 0, 0]
|
|
doc: |
|
|
Only exposed until
|
|
https://github.com/kaitai-io/kaitai_struct/issues/825 can be
|
|
fixed.
|
|
- id: tables
|
|
type: table
|
|
repeat: expr
|
|
repeat-expr: num_tables
|
|
doc: |
|
|
Describes and links to the tables present in the database.
|
|
|
|
types:
|
|
table:
|
|
doc: |
|
|
Each table is a linked list of pages containing rows of a single
|
|
type. This header describes the nature of the table and links to
|
|
its pages by index.
|
|
seq:
|
|
- id: type
|
|
type: u4
|
|
enum: page_type
|
|
if: not _root.is_ext
|
|
doc: |
|
|
Identifies the kind of rows that are found in this table.
|
|
- id: type_ext
|
|
type: u4
|
|
enum: page_type_ext
|
|
if: _root.is_ext
|
|
doc: |
|
|
Identifies the kind of rows that are found in this table from an exportExt.pdb file.
|
|
- id: empty_candidate
|
|
type: u4
|
|
- id: first_page
|
|
type: page_ref
|
|
doc: |
|
|
Links to the chain of pages making up that table. The first
|
|
page seems to always contain similar garbage patterns and
|
|
zero rows, but the next page it links to contains the start
|
|
of the meaningful data rows.
|
|
- id: last_page
|
|
type: page_ref
|
|
doc: |
|
|
Holds the index of the last page that makes up this table.
|
|
When following the linked list of pages of the table, you
|
|
either need to stop when you reach this page, or when you
|
|
notice that the `next_page` link you followed took you to a
|
|
page of a different `type`.
|
|
-webide-representation: '{type}'
|
|
|
|
page_ref:
|
|
doc: |
|
|
An index which points to a table page (its offset can be found
|
|
by multiplying the index by the `page_len` value in the file
|
|
header). This type allows the linked page to be lazy loaded.
|
|
seq:
|
|
- id: index
|
|
type: u4
|
|
doc: |
|
|
Identifies the desired page number.
|
|
instances:
|
|
body:
|
|
doc: |
|
|
When referenced, loads the specified page and parses its
|
|
contents appropriately for the type of data it contains.
|
|
io: _root._io
|
|
pos: _root.len_page * index
|
|
size: _root.len_page
|
|
type: page
|
|
|
|
page:
|
|
doc: |
|
|
A table page, consisting of a short header describing the
|
|
content of the page and linking to the next page, followed by a
|
|
heap in which row data is found. At the end of the page there is
|
|
an index which locates all rows present in the heap via their
|
|
offsets past the end of the page header.
|
|
meta:
|
|
bit-endian: le
|
|
seq:
|
|
- id: gap
|
|
contents: [0, 0, 0, 0]
|
|
doc: |
|
|
Only exposed until
|
|
https://github.com/kaitai-io/kaitai_struct/issues/825 can be
|
|
fixed.
|
|
- id: page_index
|
|
doc: Matches the index we used to look up the page, sanity check?
|
|
type: u4
|
|
- id: type
|
|
type: u4
|
|
enum: page_type
|
|
if: not _root.is_ext
|
|
doc: |
|
|
Identifies the type of information stored in the rows of this page.
|
|
- id: type_ext
|
|
type: u4
|
|
enum: page_type_ext
|
|
if: _root.is_ext
|
|
doc: |
|
|
Identifies the type of information stored in the rows of this page in an exportExt.pdb file.
|
|
- id: next_page
|
|
doc: |
|
|
Index of the next page containing this type of rows. Points past
|
|
the end of the file if there are no more.
|
|
type: page_ref
|
|
- id: sequence
|
|
type: u4
|
|
doc: |
|
|
Sequence number updated to the value of sequence from the database
|
|
header when this page is edited. The value is copied before the
|
|
value of sequence in the database header is incremented, i.e.
|
|
the one in the database header is the "next" page sequence number.
|
|
- size: 4
|
|
- id: num_row_offsets
|
|
type: b13
|
|
doc: |
|
|
Seems to hold the number of row offsets that have ever been
|
|
allocated, including those that are no longer valid.
|
|
- id: num_rows
|
|
type: b11
|
|
doc: The number of valid rows currently present in the page.
|
|
- id: page_flags
|
|
type: u1
|
|
doc: |
|
|
@flesniak said: "strange pages: 0x44, 0x64; otherwise seen: 0x24, 0x34"
|
|
- id: free_size
|
|
type: u2
|
|
doc: |
|
|
Unused space (in bytes) in the page heap, excluding the row
|
|
index at end of page.
|
|
- id: used_size
|
|
type: u2
|
|
doc: |
|
|
The number of bytes that are in use in the page heap.
|
|
- id: transaction_row_count
|
|
type: u2
|
|
doc: |
|
|
The number of rows touched in the last transaction on this page,
|
|
or 0x1fff if the last transaction failed.
|
|
- id: transaction_row_index
|
|
type: u2
|
|
doc: |
|
|
The index of the first row touched in the last transaction on this page,
|
|
or 0x1fff if the last transaction failed.
|
|
- type: u2
|
|
doc: |
|
|
@flesniak said: "1004 for strange blocks, 0 otherwise"
|
|
- type: u2
|
|
doc: |
|
|
@flesniak said: "always 0 except 1 for history pages, num
|
|
entries for strange pages?"
|
|
- id: heap
|
|
size-eos: true
|
|
if: 'false' # never true, but stores pos
|
|
instances:
|
|
is_data_page:
|
|
value: page_flags & 0x40 == 0
|
|
-webide-parse-mode: eager
|
|
heap_pos:
|
|
value: _io.pos
|
|
num_row_groups:
|
|
value: '(num_row_offsets - 1) / 16 + 1'
|
|
doc: |
|
|
The number of row groups that are present in the index. Each
|
|
group can hold up to sixteen rows, but `row_present_flags`
|
|
must be consulted to determine whether each is valid.
|
|
row_groups:
|
|
type: 'row_group(_index)'
|
|
repeat: expr
|
|
repeat-expr: num_row_groups
|
|
doc: |
|
|
The actual row groups making up the row index. Each group
|
|
can hold up to sixteen rows. Non-data pages do not have
|
|
actual rows, and attempting to parse them can crash.
|
|
if: is_data_page
|
|
|
|
row_group:
|
|
doc: |
|
|
A group of row indices, which are built backwards from the end
|
|
of the page. Holds up to sixteen row offsets, along with a bit
|
|
mask that indicates whether each row is actually present in the
|
|
table.
|
|
params:
|
|
- id: group_index
|
|
type: u2
|
|
doc: |
|
|
Identifies which group is being generated. They build backwards
|
|
from the end of the page.
|
|
instances:
|
|
base:
|
|
value: '_root.len_page - (group_index * 0x24)'
|
|
doc: |
|
|
The starting point of this group of row indices.
|
|
row_present_flags:
|
|
pos: base - 4
|
|
type: u2
|
|
doc: |
|
|
Each bit specifies whether a particular row is present. The
|
|
low order bit corresponds to the first row in this index,
|
|
whose offset immediately precedes these flag bits. The
|
|
second bit corresponds to the row whose offset precedes
|
|
that, and so on.
|
|
-webide-parse-mode: eager
|
|
transaction_row_flags:
|
|
pos: base
|
|
type: u2
|
|
doc: |
|
|
Each bit specifies whether a particular row was touched by
|
|
the last transaction on this row group, using the same
|
|
layout as row_present_flags.
|
|
rows:
|
|
type: row_ref(_index)
|
|
repeat: expr
|
|
repeat-expr: 16
|
|
doc: |
|
|
The row offsets in this group.
|
|
|
|
row_ref:
|
|
doc: |
|
|
An offset which points to a row in the table, whose actual
|
|
presence is controlled by one of the bits in
|
|
`row_present_flags`. This instance allows the row itself to be
|
|
lazily loaded, unless it is not present, in which case there is
|
|
no content to be loaded.
|
|
params:
|
|
- id: row_index
|
|
type: u2
|
|
doc: |
|
|
Identifies which row within the row index this reference
|
|
came from, so the correct flag can be checked for the row
|
|
presence and the correct row offset can be found.
|
|
instances:
|
|
ofs_row:
|
|
pos: '_parent.base - (6 + (2 * row_index))'
|
|
type: u2
|
|
doc: |
|
|
The offset of the start of the row (in bytes past the end of
|
|
the page header).
|
|
row_base:
|
|
value: ofs_row + _parent._parent.heap_pos
|
|
doc: |
|
|
The location of this row relative to the start of the page.
|
|
A variety of pointers (such as all device_sql_string values)
|
|
are calculated with respect to this position.
|
|
present:
|
|
value: '(((_parent.row_present_flags >> row_index) & 1) != 0 ? true : false)'
|
|
doc: |
|
|
Indicates whether the row index considers this row to be
|
|
present in the table. Will be `false` if the row has been
|
|
deleted.
|
|
-webide-parse-mode: eager
|
|
body:
|
|
pos: row_base
|
|
if: present and not _root.is_ext
|
|
type:
|
|
switch-on: _parent._parent.type
|
|
cases:
|
|
'page_type::albums': album_row
|
|
'page_type::artists': artist_row
|
|
'page_type::artwork': artwork_row
|
|
'page_type::colors': color_row
|
|
'page_type::genres': genre_row
|
|
'page_type::keys': key_row
|
|
'page_type::labels': label_row
|
|
'page_type::playlist_tree': playlist_tree_row
|
|
'page_type::playlist_entries': playlist_entry_row
|
|
'page_type::history_playlists': history_playlist_row
|
|
'page_type::history_entries': history_entry_row
|
|
'page_type::tracks': track_row
|
|
'page_type::browse_filter_index': browse_filter_index_row
|
|
'page_type::browse_menu': browse_menu_row
|
|
doc: |
|
|
The actual content of the row, as long as it is present.
|
|
-webide-parse-mode: eager
|
|
body_ext:
|
|
pos: row_base
|
|
if: present and _root.is_ext
|
|
type:
|
|
switch-on: _parent._parent.type_ext
|
|
cases:
|
|
'page_type_ext::tags': tag_row
|
|
'page_type_ext::tag_tracks': tag_track_row
|
|
doc: |
|
|
The actual content of the row in an exportExt.pdb file, as long as it is present.
|
|
-webide-parse-mode: eager
|
|
-webide-representation: '{body.name.body.text}{body.title.body.text}{body_ext.name.body.text} ({body.id}{body_ext.id})'
|
|
|
|
album_row:
|
|
doc: |
|
|
A row that holds an album name and ID.
|
|
seq:
|
|
- id: subtype
|
|
type: u2
|
|
doc: |
|
|
Usually 0x80, but 0x84 means we have a long name offset
|
|
embedded in the row.
|
|
- id: index_shift
|
|
type: u2
|
|
doc: TODO name from @flesniak, but what does it mean?
|
|
- type: u4
|
|
- id: artist_id
|
|
type: u4
|
|
doc: |
|
|
Identifies the artist associated with the album.
|
|
- id: id
|
|
type: u4
|
|
doc: |
|
|
The unique identifier by which this album can be requested
|
|
and linked from other rows (such as tracks).
|
|
- type: u4
|
|
- type: u1
|
|
doc: |
|
|
@flesniak says: "always 0x03, maybe an unindexed empty string"
|
|
- id: ofs_name_near
|
|
type: u1
|
|
doc: |
|
|
The location of the variable-length name string, relative to
|
|
the start of this row, unless subtype is 0x84.
|
|
instances:
|
|
ofs_name_far:
|
|
pos: _parent.row_base + 0x16
|
|
type: u2
|
|
doc: |
|
|
For names that might be further than 0xff bytes from the
|
|
start of this row, this holds a two-byte offset, and is
|
|
signalled by the subtype value.
|
|
if: subtype & 0x04 == 0x04
|
|
name:
|
|
pos: '_parent.row_base + (subtype & 0x04 == 0x04? ofs_name_far : ofs_name_near)'
|
|
type: device_sql_string
|
|
doc: |
|
|
The name of this album.
|
|
-webide-parse-mode: eager
|
|
|
|
artist_row:
|
|
doc: |
|
|
A row that holds an artist name and ID.
|
|
seq:
|
|
- id: subtype
|
|
type: u2
|
|
doc: |
|
|
Usually 0x60, but 0x64 means we have a long name offset
|
|
embedded in the row.
|
|
- id: index_shift
|
|
type: u2
|
|
doc: TODO name from @flesniak, but what does it mean?
|
|
- id: id
|
|
type: u4
|
|
doc: |
|
|
The unique identifier by which this artist can be requested
|
|
and linked from other rows (such as tracks).
|
|
- type: u1
|
|
doc: |
|
|
@flesniak says: "always 0x03, maybe an unindexed empty string"
|
|
- id: ofs_name_near
|
|
type: u1
|
|
doc: |
|
|
The location of the variable-length name string, relative to
|
|
the start of this row, unless subtype is 0x64.
|
|
instances:
|
|
ofs_name_far:
|
|
pos: _parent.row_base + 0x0a
|
|
type: u2
|
|
doc: |
|
|
For names that might be further than 0xff bytes from the
|
|
start of this row, this holds a two-byte offset, and is
|
|
signalled by the subtype value.
|
|
if: subtype & 0x04 == 0x04
|
|
name:
|
|
pos: '_parent.row_base + (subtype & 0x04 == 0x04? ofs_name_far : ofs_name_near)'
|
|
type: device_sql_string
|
|
doc: |
|
|
The name of this artist.
|
|
-webide-parse-mode: eager
|
|
|
|
artwork_row:
|
|
doc: |
|
|
A row that holds the path to an album art image file and the
|
|
associated artwork ID.
|
|
seq:
|
|
- id: id
|
|
type: u4
|
|
doc: |
|
|
The unique identifier by which this art can be requested
|
|
and linked from other rows (such as tracks).
|
|
- id: path
|
|
type: device_sql_string
|
|
doc: |
|
|
The variable-length file path string at which the art file
|
|
can be found.
|
|
-webide-representation: '{path.body.text}'
|
|
|
|
color_row:
|
|
doc: |
|
|
A row that holds a color name and the associated ID.
|
|
seq:
|
|
- size: 5
|
|
- id: id
|
|
type: u2
|
|
doc: |
|
|
The unique identifier by which this color can be requested
|
|
and linked from other rows (such as tracks).
|
|
- type: u1
|
|
- id: name
|
|
type: device_sql_string
|
|
doc: |
|
|
The variable-length string naming the color.
|
|
|
|
genre_row:
|
|
doc: |
|
|
A row that holds a genre name and the associated ID.
|
|
seq:
|
|
- id: id
|
|
type: u4
|
|
doc: |
|
|
The unique identifier by which this genre can be requested
|
|
and linked from other rows (such as tracks).
|
|
- id: name
|
|
type: device_sql_string
|
|
doc: |
|
|
The variable-length string naming the genre.
|
|
|
|
key_row:
|
|
doc: |
|
|
A row that holds a musical key and the associated ID.
|
|
seq:
|
|
- id: id
|
|
type: u4
|
|
doc: |
|
|
The unique identifier by which this key can be requested
|
|
and linked from other rows (such as tracks).
|
|
- id: id2
|
|
type: u4
|
|
doc: |
|
|
Seems to be a second copy of the ID?
|
|
- id: name
|
|
type: device_sql_string
|
|
doc: |
|
|
The variable-length string naming the key.
|
|
|
|
label_row:
|
|
doc: |
|
|
A row that holds a label name and the associated ID.
|
|
seq:
|
|
- id: id
|
|
type: u4
|
|
doc: |
|
|
The unique identifier by which this label can be requested
|
|
and linked from other rows (such as tracks).
|
|
- id: name
|
|
type: device_sql_string
|
|
doc: |
|
|
The variable-length string naming the label.
|
|
|
|
playlist_tree_row:
|
|
doc: |
|
|
A row that holds a playlist name, ID, indication of whether it
|
|
is an ordinary playlist or a folder of other playlists, a link
|
|
to its parent folder, and its sort order.
|
|
seq:
|
|
- id: parent_id
|
|
type: u4
|
|
doc: |
|
|
The ID of the `playlist_tree_row` in which this one can be
|
|
found, or `0` if this playlist exists at the root level.
|
|
- size: 4
|
|
- id: sort_order
|
|
type: u4
|
|
doc: |
|
|
The order in which the entries of this playlist are sorted.
|
|
- id: id
|
|
type: u4
|
|
doc: |
|
|
The unique identifier by which this playlist or folder can
|
|
be requested and linked from other rows.
|
|
- id: raw_is_folder
|
|
type: u4
|
|
doc: |
|
|
Has a non-zero value if this is actually a folder rather
|
|
than a playlist.
|
|
- id: name
|
|
type: device_sql_string
|
|
doc: |
|
|
The variable-length string naming the playlist.
|
|
instances:
|
|
is_folder:
|
|
value: raw_is_folder != 0
|
|
-webide-parse-mode: eager
|
|
|
|
playlist_entry_row:
|
|
doc: |
|
|
A row that associates a track with a position in a playlist.
|
|
seq:
|
|
- id: entry_index
|
|
type: u4
|
|
doc: |
|
|
The position within the playlist represented by this entry.
|
|
- id: track_id
|
|
type: u4
|
|
doc: |
|
|
The track found at this position in the playlist.
|
|
- id: playlist_id
|
|
type: u4
|
|
doc: |
|
|
The playlist to which this entry belongs.
|
|
|
|
history_playlist_row:
|
|
doc: |
|
|
A row that holds a history playlist ID and name, linking to
|
|
the track IDs captured during a performance on the player.
|
|
seq:
|
|
- id: id
|
|
type: u4
|
|
doc: |
|
|
The unique identifier by which this history playlist can
|
|
be requested.
|
|
- id: name
|
|
type: device_sql_string
|
|
doc: |
|
|
The variable-length string naming the playlist.
|
|
|
|
browse_filter_index_row:
|
|
doc: |
|
|
A row in the `browse_filter_index` (was `unknown_17`) table. Lists the columns
|
|
that appear in the player's "Filter by ->" sidebar, in display order. The
|
|
reverse-engineered semantics: each row enumerates one filterable column. The
|
|
first group (`d == 0`) has 12 rows that look like type/icon descriptors; the
|
|
second group (`d == 1..10`) defines the navigation entries with `d` as the
|
|
sort position. The 22 column ids that appear here are exactly the
|
|
"filterable" subset of the 27 columns -- CUE, COMMENTS, DJ PLAY COUNT,
|
|
DEFAULT, ALPHABET do not appear. Verified byte-for-byte identical across
|
|
multiple working rekordbox exports.
|
|
seq:
|
|
- id: column_id
|
|
type: u2
|
|
doc: |
|
|
The column this row references; corresponds to `id` in the `columns` table.
|
|
- id: secondary_column
|
|
type: u2
|
|
doc: |
|
|
A second column id whose role is not fully nailed down. For rows in the
|
|
`d == 0` group it usually equals `column_id + 1`; for the navigation
|
|
group it varies. Likely a secondary sort key or an internal ID-lookup
|
|
target rather than something rendered to the user (the KEY filter does
|
|
not show CUEs even though `secondary_column == CUE` here).
|
|
- id: filter_type
|
|
type: u2
|
|
doc: |
|
|
Filter kind code. Observed values: 0x163 (=355) for textual filters,
|
|
0x104-0x106 (=260-262) for numeric/enum filters, 0x63 (=99) for
|
|
non-leaf navigation entries, 0x263 (=611) for the MATCHING entry,
|
|
0x01-0x03 for the ARTIST/ALBUM/TRACK leaf entries.
|
|
- id: sort_index
|
|
type: u2
|
|
doc: |
|
|
Zero for the first group, otherwise 1..10 giving the display order of
|
|
this row in the top-level filter list (1 = first, 10 = last).
|
|
|
|
browse_menu_row:
|
|
doc: |
|
|
A row in the `browse_menu` (was `unknown_18`) table. Together with
|
|
`browse_filter_index`, this drives the player's top-level browse navigation.
|
|
Semantic model: `display_column` is the column shown in the menu;
|
|
`sort_column` is the column used to sort/key it. When the two are equal the
|
|
entry opens a drilldown of that column's distinct values (e.g.
|
|
`(ARTIST, ARTIST)` -> list of artists). When they differ the entry opens a
|
|
flat list of `display_column` rows sorted by `sort_column` (e.g.
|
|
`(BPM, TRACK)` -> tracks sorted by BPM). Verified content is identical
|
|
across all observed working exports.
|
|
seq:
|
|
- id: sort_column
|
|
type: u2
|
|
doc: |
|
|
The column used to order the list shown by this menu entry.
|
|
- id: display_column
|
|
type: u2
|
|
doc: |
|
|
The column whose rows the entry lists. If `display_column == sort_column`,
|
|
you get a drilldown of that column's distinct values; otherwise a flat
|
|
list ordered by `sort_column`.
|
|
- id: flags
|
|
type: u2
|
|
doc: |
|
|
Two packed bytes. Low byte is non-zero only for the "tracks sorted by
|
|
BPM" row (=2, possibly a rendering hint). High byte (0x01..0x07) gives
|
|
the display order of the top-level entries (1=DEFAULT, 2=ALPHABET,
|
|
3=ARTIST, 4=ALBUM, 5=tracks-by-BPM, 6=BPM-by-rating, 7=cues-by-key).
|
|
A value of 1 marks the row as a secondary sort pair rather than a
|
|
top-level menu item.
|
|
- id: reserved
|
|
type: u2
|
|
doc: Always zero in observed exports.
|
|
|
|
history_entry_row:
|
|
doc: |
|
|
A row that associates a track with a position in a history playlist.
|
|
seq:
|
|
- id: track_id
|
|
type: u4
|
|
doc: |
|
|
The track found at this position in the playlist.
|
|
- id: playlist_id
|
|
type: u4
|
|
doc: |
|
|
The history playlist to which this entry belongs.
|
|
- id: entry_index
|
|
type: u4
|
|
doc: |
|
|
The position within the playlist represented by this entry.
|
|
|
|
track_row:
|
|
doc: |
|
|
A row that describes a track that can be played, with many
|
|
details about the music, and links to other tables like artists,
|
|
albums, keys, etc.
|
|
seq:
|
|
- id: subtype
|
|
type: u2
|
|
doc: |
|
|
Seems to always be 0x24, and bit 0x04 being set means it uses sixteen-bit offsets,
|
|
as it does in other tables. Track rows are always big enough to need that size offsets.
|
|
- id: index_shift
|
|
type: u2
|
|
doc: TODO name from @flesniak, but what does it mean?
|
|
- id: bitmask
|
|
type: u4
|
|
doc: TODO what do the bits mean?
|
|
- id: sample_rate
|
|
type: u4
|
|
doc: |
|
|
Playback sample rate of the audio file.
|
|
- id: composer_id
|
|
type: u4
|
|
doc: |
|
|
References a row in the artist table if the composer is
|
|
known.
|
|
- id: file_size
|
|
type: u4
|
|
doc: |
|
|
The length of the audio file, in bytes.
|
|
- type: u4
|
|
doc: |
|
|
Some ID? Purpose as yet unknown.
|
|
- type: u2
|
|
doc: |
|
|
From @flesniak: "always 19048?"
|
|
- type: u2
|
|
doc: |
|
|
From @flesniak: "always 30967?"
|
|
- id: artwork_id
|
|
type: u4
|
|
doc: |
|
|
References a row in the artwork table if there is album art.
|
|
- id: key_id
|
|
type: u4
|
|
doc: |
|
|
References a row in the keys table if the track has a known
|
|
main musical key.
|
|
- id: original_artist_id
|
|
type: u4
|
|
doc: |
|
|
References a row in the artwork table if this is a cover
|
|
performance and the original artist is known.
|
|
- id: label_id
|
|
type: u4
|
|
doc: |
|
|
References a row in the labels table if the track has a
|
|
known record label.
|
|
- id: remixer_id
|
|
type: u4
|
|
doc: |
|
|
References a row in the artists table if the track has a
|
|
known remixer.
|
|
- id: bitrate
|
|
type: u4
|
|
doc: |
|
|
Playback bit rate of the audio file.
|
|
- id: track_number
|
|
type: u4
|
|
doc: |
|
|
The position of the track within an album.
|
|
- id: tempo
|
|
type: u4
|
|
doc: |
|
|
The tempo at the start of the track in beats per minute,
|
|
multiplied by 100.
|
|
- id: genre_id
|
|
type: u4
|
|
doc: |
|
|
References a row in the genres table if the track has a
|
|
known musical genre.
|
|
- id: album_id
|
|
type: u4
|
|
doc: |
|
|
References a row in the albums table if the track has a
|
|
known album.
|
|
- id: artist_id
|
|
type: u4
|
|
doc: |
|
|
References a row in the artists table if the track has a
|
|
known performer.
|
|
- id: id
|
|
type: u4
|
|
doc: |
|
|
The id by which this track can be looked up; players will
|
|
report this value in their status packets when they are
|
|
playing the track.
|
|
- id: disc_number
|
|
type: u2
|
|
doc: |
|
|
The number of the disc on which this track is found, if it
|
|
is known to be part of a multi-disc album.
|
|
- id: play_count
|
|
type: u2
|
|
doc: |
|
|
The number of times this track has been played.
|
|
- id: year
|
|
type: u2
|
|
doc: |
|
|
The year in which this track was released.
|
|
- id: sample_depth
|
|
type: u2
|
|
doc: |
|
|
The number of bits per sample of the audio file.
|
|
- id: duration
|
|
type: u2
|
|
doc: |
|
|
The length, in seconds, of the track when played at normal
|
|
speed.
|
|
- type: u2
|
|
doc: |
|
|
From @flesniak: "always 41?"
|
|
- id: color_id
|
|
type: u1
|
|
doc: |
|
|
References a row in the colors table if the track has been
|
|
assigned a color.
|
|
- id: rating
|
|
type: u1
|
|
doc: |
|
|
The number of stars to display for the track, 0 to 5.
|
|
- type: u2
|
|
doc: |
|
|
From @flesniak: "always 1?"
|
|
- type: u2
|
|
doc: |
|
|
From @flesniak: "alternating 2 or 3"
|
|
- id: ofs_strings
|
|
type: u2
|
|
repeat: expr
|
|
repeat-expr: 21
|
|
doc: |
|
|
The location, relative to the start of this row, of a
|
|
variety of variable-length strings.
|
|
instances:
|
|
isrc:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[0]
|
|
doc: |
|
|
International Standard Recording Code of track
|
|
when known (in mangled format).
|
|
-webide-parse-mode: eager
|
|
texter:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[1]
|
|
doc: |
|
|
A string of unknown purpose, which @flesniak named.
|
|
-webide-parse-mode: eager
|
|
unknown_string_2:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[2]
|
|
doc: |
|
|
A string of unknown purpose; @flesniak said "thought
|
|
track number -> wrong!"
|
|
unknown_string_3:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[3]
|
|
doc: |
|
|
A string of unknown purpose; @flesniak said "strange
|
|
strings, often zero length, sometimes low binary values
|
|
0x01/0x02 as content"
|
|
unknown_string_4:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[4]
|
|
doc: |
|
|
A string of unknown purpose; @flesniak said "strange
|
|
strings, often zero length, sometimes low binary values
|
|
0x01/0x02 as content"
|
|
-webide-parse-mode: eager
|
|
message:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[5]
|
|
doc: |
|
|
A string of unknown purpose, which @flesniak named.
|
|
-webide-parse-mode: eager
|
|
kuvo_public:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[6]
|
|
doc: |
|
|
A string whose value is always either empty or "ON", and
|
|
which apparently for some insane reason is used, rather than
|
|
a single bit somewhere, to control whether the track
|
|
information is visible on Kuvo.
|
|
-webide-parse-mode: eager
|
|
autoload_hot_cues:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[7]
|
|
doc: |
|
|
A string whose value is always either empty or "ON", and
|
|
which apparently for some insane reason is used, rather than
|
|
a single bit somewhere, to control whether hot-cues are
|
|
auto-loaded for the track.
|
|
-webide-parse-mode: eager
|
|
unknown_string_5:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[8]
|
|
doc: |
|
|
A string of unknown purpose.
|
|
-webide-parse-mode: eager
|
|
unknown_string_6:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[9]
|
|
doc: |
|
|
A string of unknown purpose, usually empty.
|
|
-webide-parse-mode: eager
|
|
date_added:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[10]
|
|
doc: |
|
|
A string containing the date this track was added to the collection.
|
|
-webide-parse-mode: eager
|
|
release_date:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[11]
|
|
doc: |
|
|
A string containing the date this track was released, if known.
|
|
-webide-parse-mode: eager
|
|
mix_name:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[12]
|
|
doc: |
|
|
A string naming the remix of the track, if known.
|
|
-webide-parse-mode: eager
|
|
unknown_string_7:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[13]
|
|
doc: |
|
|
A string of unknown purpose, usually empty.
|
|
-webide-parse-mode: eager
|
|
analyze_path:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[14]
|
|
doc: |
|
|
The file path of the track analysis, which allows rapid
|
|
seeking to particular times in variable bit-rate files,
|
|
jumping to particular beats, visual waveform previews, and
|
|
stores cue points and loops.
|
|
-webide-parse-mode: eager
|
|
analyze_date:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[15]
|
|
doc: |
|
|
A string containing the date this track was analyzed by rekordbox.
|
|
-webide-parse-mode: eager
|
|
comment:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[16]
|
|
doc: |
|
|
The comment assigned to the track by the DJ, if any.
|
|
-webide-parse-mode: eager
|
|
title:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[17]
|
|
doc: |
|
|
The title of the track.
|
|
-webide-parse-mode: eager
|
|
unknown_string_8:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[18]
|
|
doc: |
|
|
A string of unknown purpose, usually empty.
|
|
-webide-parse-mode: eager
|
|
filename:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[19]
|
|
doc: |
|
|
The file name of the track audio file.
|
|
-webide-parse-mode: eager
|
|
file_path:
|
|
type: device_sql_string
|
|
pos: _parent.row_base + ofs_strings[20]
|
|
doc: |
|
|
The file path of the track audio file.
|
|
-webide-parse-mode: eager
|
|
|
|
tag_row:
|
|
doc: |
|
|
A row that holds a tag name and its ID (found only in exportExt.pdb files).
|
|
seq:
|
|
- id: subtype
|
|
type: u2
|
|
doc: |
|
|
Usually 0x0680, but 0x0684 means we have a long name offset
|
|
embedded in the row.
|
|
- id: tag_index
|
|
type: u2
|
|
doc: |
|
|
Increasing index for each row in multiples of 0x20.
|
|
- type: u8
|
|
doc: |
|
|
Seems to always be zero.
|
|
- id: category
|
|
type: u4
|
|
doc: |
|
|
The ID of the tag category this tag belongs to.
|
|
If this row represents a tag category, this field is zero.
|
|
- id: category_pos
|
|
type: u4
|
|
doc: |
|
|
The zero-based position of this tag in its category.
|
|
If this row represents a tag category, the zero-based position of the category itself in the category list.
|
|
- id: id
|
|
type: u4
|
|
doc: |
|
|
The ID of this tag or tag category.
|
|
Referenced by tag_track_row if this row is a tag.
|
|
- id: raw_is_category
|
|
type: u4
|
|
doc: |
|
|
Non-zero when this row stores a tag category instead of a tag.
|
|
- type: u1
|
|
doc: |
|
|
@flesniak says: "always 0x03, maybe an unindexed empty string"
|
|
- id: ofs_name_near
|
|
type: u1
|
|
doc: |
|
|
The location of the variable-length name string, relative to
|
|
the start of this row, unless subtype is 0x64.
|
|
- id: ofs_unknown_near
|
|
type: u1
|
|
doc: |
|
|
Offset to a string that seems always to be empty.
|
|
instances:
|
|
is_category:
|
|
value: raw_is_category != 0
|
|
doc: |
|
|
Indicates whether this row stores a tag category instead of a tag.
|
|
-webide-parse-mode: eager
|
|
ofs_name_far:
|
|
pos: _parent.row_base + 0x1e
|
|
type: u2
|
|
doc: |
|
|
For names that might be further than 0xff bytes from the
|
|
start of this row, this holds a two-byte offset, and is
|
|
signalled by the subtype value.
|
|
if: subtype & 0x04 == 0x04
|
|
name:
|
|
pos: '_parent.row_base + (subtype & 0x04 == 0x04? ofs_name_far : ofs_name_near)'
|
|
type: device_sql_string
|
|
doc: |
|
|
The name of this tag or tag category.
|
|
-webide-parse-mode: eager
|
|
|
|
tag_track_row:
|
|
doc: |
|
|
A row that associates a track and a tag (found only in exportExt.pdb files).
|
|
seq:
|
|
- type: u4
|
|
doc: |
|
|
Seems to always be zero.
|
|
- id: track_id
|
|
type: u4
|
|
doc: |
|
|
The ID of the track that has a tag assigned to it.
|
|
- id: tag_id
|
|
type: u4
|
|
doc: |
|
|
The ID of the tag that has been assigned to a track.
|
|
- type: u4
|
|
doc: |
|
|
Seems to always be 0x03 0x00 0x00 0x00.
|
|
|
|
device_sql_string:
|
|
doc: |
|
|
A variable length string which can be stored in a variety of
|
|
different encodings.
|
|
seq:
|
|
- id: length_and_kind
|
|
type: u1
|
|
doc: |
|
|
Mangled length of an ordinary ASCII string if odd, or a flag
|
|
indicating another encoding with a longer length value to
|
|
follow.
|
|
- id: body
|
|
type:
|
|
switch-on: length_and_kind
|
|
cases:
|
|
0x40: device_sql_long_ascii
|
|
0x90: device_sql_long_utf16le
|
|
_: device_sql_short_ascii(length_and_kind)
|
|
-webide-parse-mode: eager
|
|
-webide-representation: '{body.text}'
|
|
|
|
device_sql_short_ascii:
|
|
doc: |
|
|
An ASCII-encoded string up to 127 bytes long.
|
|
params:
|
|
- id: length_and_kind
|
|
type: u1
|
|
doc: |
|
|
Contains the actual length, incremented, doubled, and
|
|
incremented again. Go figure.
|
|
seq:
|
|
- id: text
|
|
type: str
|
|
size: length - 1
|
|
encoding: ASCII
|
|
doc: |
|
|
The content of the string.
|
|
instances:
|
|
length:
|
|
value: '(length_and_kind >> 1)'
|
|
doc: |
|
|
the length extracted of the entire device_sql_short_ascii type
|
|
-webide-parse-mode: eager
|
|
|
|
device_sql_long_ascii:
|
|
doc: |
|
|
An ASCII-encoded string preceded by a two-byte length field in a four-byte header.
|
|
seq:
|
|
- id: length
|
|
type: u2
|
|
doc: |
|
|
Contains the length of the string in bytes.
|
|
- type: u1
|
|
- id: text
|
|
type: str
|
|
size: length - 4
|
|
encoding: ASCII
|
|
doc: |
|
|
The content of the string.
|
|
|
|
device_sql_long_utf16le:
|
|
doc: |
|
|
A UTF-16LE-encoded string preceded by a two-byte length field in a four-byte header.
|
|
seq:
|
|
- id: length
|
|
type: u2
|
|
doc: |
|
|
Contains the length of the string in bytes, plus four trailing bytes that must be ignored.
|
|
- type: u1
|
|
- id: text
|
|
type: str
|
|
size: length - 4
|
|
encoding: UTF-16LE
|
|
doc: |
|
|
The content of the string.
|
|
|
|
enums:
|
|
page_type:
|
|
0:
|
|
id: tracks
|
|
doc: |
|
|
Holds rows describing tracks, such as their title, artist,
|
|
genre, artwork ID, playing time, etc.
|
|
1:
|
|
id: genres
|
|
doc: |
|
|
Holds rows naming musical genres, for reference by tracks and searching.
|
|
2:
|
|
id: artists
|
|
doc: |
|
|
Holds rows naming artists, for reference by tracks and searching.
|
|
3:
|
|
id: albums
|
|
doc: |
|
|
Holds rows naming albums, for reference by tracks and searching.
|
|
4:
|
|
id: labels
|
|
doc: |
|
|
Holds rows naming music labels, for reference by tracks and searching.
|
|
5:
|
|
id: keys
|
|
doc: |
|
|
Holds rows naming musical keys, for reference by tracks and searching.
|
|
6:
|
|
id: colors
|
|
doc: |
|
|
Holds rows naming color labels, for reference by tracks and searching.
|
|
7:
|
|
id: playlist_tree
|
|
doc: |
|
|
Holds rows that describe the hierarchical tree structure of
|
|
available playlists and folders grouping them.
|
|
8:
|
|
id: playlist_entries
|
|
doc: |
|
|
Holds rows that enumerate the tracks found in playlists and
|
|
the playlists they belong to.
|
|
9:
|
|
id: unknown_9
|
|
10:
|
|
id: unknown_10
|
|
11:
|
|
id: history_playlists
|
|
doc: |
|
|
Holds rows that assign IDs and give names to the history playlists
|
|
that have been captured by the player, such as "HISTORY 001".
|
|
12:
|
|
id: history_entries
|
|
doc: |
|
|
Holds rows that enumerate the tracks found in history playlists
|
|
and the playlists they belong to.
|
|
13:
|
|
id: artwork
|
|
doc: |
|
|
Holds rows pointing to album artwork images.
|
|
14:
|
|
id: unknown_14
|
|
15:
|
|
id: unknown_15
|
|
16:
|
|
id: columns
|
|
doc: |
|
|
TODO figure out and explain
|
|
17:
|
|
id: browse_filter_index
|
|
doc: |
|
|
Drives the player's "Filter by ->" sidebar. Each row is four little-endian u2s
|
|
`(a, b, c, d)` -- see `browse_filter_index_row` -- where `a` is the column the
|
|
menu item shows, `d` is its sort order in the filter list, and `c` is a type
|
|
code (0x163/0x104/0x105/0x106 for the various filter kinds). Exactly the
|
|
columns not in this table are the "non-filterable" ones (CUE, COMMENTS,
|
|
DJ PLAY COUNT, DEFAULT, ALPHABET); 22 of the 27 column ids appear here.
|
|
Verified content is identical across multiple working exports, so this is
|
|
effectively a fixed table rather than per-library content. Was `unknown_17`.
|
|
18:
|
|
id: browse_menu
|
|
doc: |
|
|
Drives the player's top-level browse menu. Each row is four little-endian u2s
|
|
`(a, b, c, d)` -- see `browse_menu_row` -- where `b` is the column the menu
|
|
entry *displays* and `a` is the column it *sorts by*. When `a == b` the entry
|
|
opens a drilldown of that column's distinct values (e.g. ARTIST -> list of
|
|
artists). When `a != b` the entry opens a flat list of `b` rows sorted by
|
|
`a` (e.g. (BPM, TRACK) -> tracks sorted by BPM). Rows split into two groups:
|
|
rows where `c == 1` are secondary sort pairs (10 of them); rows where `c`
|
|
is `(0x100 * n)` define the top-level menu items with `n` as the display
|
|
position (DEFAULT, ALPHABET, ARTIST, ALBUM, "tracks sorted by BPM",
|
|
"BPM by rating", "cues by key"). Verified content is identical across all
|
|
observed working exports. Was `unknown_18`.
|
|
19:
|
|
id: history
|
|
doc: |
|
|
Holds information to help rekordbox sync history playlists.
|
|
|
|
page_type_ext:
|
|
0:
|
|
id: unknown_0
|
|
1:
|
|
id: unknown_1
|
|
2:
|
|
id: unknown_2
|
|
3:
|
|
id: tags
|
|
doc: |
|
|
Holds rows naming tags.
|
|
4:
|
|
id: tag_tracks
|
|
doc: |
|
|
Holds rows associating tags and tracks.
|
|
5:
|
|
id: unknown_5
|
|
6:
|
|
id: unknown_6
|
|
7:
|
|
id: unknown_7
|
|
8:
|
|
id: unknown_8
|