rekordcrate/pdb/
mod.rs

1// Copyright (c) 2026 Jan Holthuis <jan.holthuis@rub.de>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy
4// of the MPL was not distributed with this file, You can obtain one at
5// http://mozilla.org/MPL/2.0/.
6//
7// SPDX-License-Identifier: MPL-2.0
8
9//! Parser for Pioneer DeviceSQL database exports (PDB).
10//!
11//! The Rekordbox DJ software uses writes PDB files to `/PIONEER/rekordbox/export.pdb`.
12//!
13//! Most of the file format has been reverse-engineered by Henry Betts, Fabian Lesniak and James
14//! Elliott.
15//!
16//! - <https://github.com/Deep-Symmetry/crate-digger/blob/master/doc/Analysis.pdf>
17//! - <https://djl-analysis.deepsymmetry.org/rekordbox-export-analysis/exports.html>
18//! - <https://github.com/henrybetts/Rekordbox-Decoding>
19//! - <https://github.com/flesniak/python-prodj-link/tree/master/prodj/pdblib>
20
21pub mod bitfields;
22pub mod ext;
23pub mod offset_array;
24pub mod string;
25
26use bitfields::{PackedRowCounts, PageFlags};
27use offset_array::{OffsetArrayContainer, OffsetArrayItems};
28
29#[cfg(test)]
30mod test_roundtrip;
31
32#[cfg(test)]
33mod test_modification;
34
35use std::collections::BTreeMap;
36use std::fmt;
37
38use crate::pdb::ext::{ExtPageType, ExtRow};
39use crate::pdb::offset_array::OffsetSize;
40use crate::pdb::string::DeviceSQLString;
41use crate::util::{parse_at_offsets, write_at_offsets, ColorIndex, FileType};
42use binrw::{
43    binrw,
44    io::{Read, Seek, SeekFrom, Write},
45    BinRead, BinResult, BinWrite, Endian,
46};
47use thiserror::Error;
48
49/// An error that can occur when parsing a PDB file.
50#[derive(Debug, Error)]
51pub enum PdbError {
52    /// An invalid value was passed when creating a `PageIndex`.
53    #[error("Invalid page index value: {0:#X}")]
54    InvalidPageIndex(u32),
55    /// Invalid flags were passed when creating an `IndexEntry`.
56    #[error("Invalid index flags (expected max 3 bits): {0:#b}")]
57    InvalidIndexFlags(u8),
58    /// A row was added to a full `RowGroup`.
59    #[error("Cannot add row to a full row group (max 16 rows)")]
60    RowGroupFull,
61}
62
63/// The type of the database were looking at.
64/// This influences the meaning of the the pagetypes found in tables.
65#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
66pub enum DatabaseType {
67    #[default] // use plain by default for use of migration
68    /// Standard export.pdb files.
69    Plain,
70    /// Extended exportExt.pdb files.
71    Ext,
72}
73
74/// The type of pages found inside a `Table`.
75#[binrw]
76#[derive(Debug, PartialEq, Eq, Clone, Copy)]
77#[brw(little)]
78#[br(import(db_type: DatabaseType))]
79pub enum PageType {
80    #[br(pre_assert(db_type == DatabaseType::Plain))]
81    /// Pagetypes present in `export.pdb` files.
82    Plain(PlainPageType),
83    #[br(pre_assert(db_type == DatabaseType::Ext))]
84    /// Pagetypes present in `exportExt.pdb` files.
85    Ext(ExtPageType),
86    /// Unknown page type.
87    Unknown(u32),
88}
89
90/// The type of pages found inside a `Table` of export.pdb files.
91#[binrw]
92#[derive(Debug, PartialEq, Eq, Clone, Copy)]
93#[brw(little)]
94pub enum PlainPageType {
95    /// Holds rows of track metadata, such as title, artist, genre, artwork ID, playing time, etc.
96    #[brw(magic = 0u32)]
97    Tracks,
98    /// Holds rows of musical genres, for reference by tracks and searching.
99    #[brw(magic = 1u32)]
100    Genres,
101    /// Holds rows of artists, for reference by tracks and searching.
102    #[brw(magic = 2u32)]
103    Artists,
104    /// Holds rows of albums, for reference by tracks and searching.
105    #[brw(magic = 3u32)]
106    Albums,
107    /// Holds rows of music labels, for reference by tracks and searching.
108    #[brw(magic = 4u32)]
109    Labels,
110    /// Holds rows of musical keys, for reference by tracks, searching, and key matching.
111    #[brw(magic = 5u32)]
112    Keys,
113    /// Holds rows of color labels, for reference  by tracks and searching.
114    #[brw(magic = 6u32)]
115    Colors,
116    /// Holds rows that describe the hierarchical tree structure of available playlists and folders
117    /// grouping them.
118    #[brw(magic = 7u32)]
119    PlaylistTree,
120    /// Holds rows that links tracks to playlists, in the right order.
121    #[brw(magic = 8u32)]
122    PlaylistEntries,
123    /// Holds rows of history playlists, i.e. playlists that are recorded every time the device is
124    /// mounted by a player.
125    #[brw(magic = 11u32)]
126    HistoryPlaylists,
127    /// Holds rows that links tracks to history playlists, in the right order.
128    #[brw(magic = 12u32)]
129    HistoryEntries,
130    /// Holds rows pointing to album artwork images.
131    #[brw(magic = 13u32)]
132    Artwork,
133    /// Contains the metadata categories by which Tracks can be browsed by.
134    #[brw(magic = 16u32)]
135    Columns,
136    /// Manages the active menus on the CDJ.
137    #[brw(magic = 17u32)]
138    Menu,
139    /// Holds information used by rekordbox to synchronize history playlists (not yet studied).
140    #[brw(magic = 19u32)]
141    History,
142}
143
144/// Points to a table page and can be used to calculate the page's file offset by multiplying it
145/// with the page size (found in the file header).
146#[binrw]
147#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
148#[brw(little)]
149pub struct PageIndex(u32);
150
151impl TryFrom<u32> for PageIndex {
152    type Error = PdbError;
153
154    fn try_from(value: u32) -> Result<Self, Self::Error> {
155        if value < 0x03FF_FFFF {
156            Ok(Self(value))
157        } else {
158            Err(PdbError::InvalidPageIndex(value))
159        }
160    }
161}
162
163impl PageIndex {
164    /// Calculate the absolute file offset of the page in the PDB file for the given `page_size`.
165    #[must_use]
166    pub fn offset(&self, page_size: u32) -> u64 {
167        u64::from(self.0) * u64::from(page_size)
168    }
169}
170
171/// Tables are linked lists of pages containing rows of a single type, which are organized
172/// into groups.
173#[binrw]
174#[derive(Debug, PartialEq, Eq, Clone)]
175#[brw(little)]
176#[brw(import(db_type: DatabaseType))]
177pub struct Table {
178    /// Identifies the type of rows that this table contains.
179    #[br(args(db_type))]
180    pub page_type: PageType,
181    /// Unknown field, maybe links to a chain of empty pages if the database is ever garbage
182    /// collected (?).
183    #[allow(dead_code)]
184    empty_candidate: u32,
185    /// Index of the first page that belongs to this table.
186    ///
187    /// *Note:* The first page apparently does not contain any rows. If the table is non-empty, the
188    /// actual row data can be found in the pages after.
189    pub first_page: PageIndex,
190    /// Index of the last page that belongs to this table.
191    pub last_page: PageIndex,
192}
193
194/// The PDB header structure, including the list of tables.
195#[binrw]
196#[derive(Debug, PartialEq, Eq, Clone)]
197#[brw(little)]
198#[brw(import(db_type: DatabaseType))]
199pub struct Header {
200    // Unknown purpose, perhaps an unoriginal signature, seems to always have the value 0.
201    #[brw(magic = 0u32)]
202    /// Size of a single page in bytes.
203    ///
204    /// The byte offset of a page can be calculated by multiplying a page index with this value.
205    pub page_size: u32,
206    /// Number of tables.
207    pub num_tables: u32,
208    /// Unknown field, not used as any `empty_candidate`, points past end of file.
209    pub next_unused_page: PageIndex,
210    /// Unknown field.
211    pub unknown: u32,
212    /// Always incremented by at least one, sometimes by two or three.
213    pub sequence: u32,
214    // The gap seems to be always zero.
215    #[brw(magic = 0u32)]
216    /// Each table is a linked list of pages containing rows of a particular type.
217    #[br(count = num_tables, args {inner: (db_type,)})]
218    #[bw(args(db_type))]
219    pub tables: Vec<Table>,
220}
221
222impl Header {
223    /// Returns pages for the given Table.
224    pub fn read_pages<R: Read + Seek>(
225        &self,
226        reader: &mut R,
227        _: Endian,
228        args: (&PageIndex, &PageIndex, DatabaseType),
229    ) -> BinResult<Vec<Page>> {
230        let endian = Endian::Little;
231        let (first_page, last_page, db_type) = args;
232
233        let mut pages = vec![];
234        let mut page_index = first_page.clone();
235        loop {
236            let page_offset = SeekFrom::Start(page_index.offset(self.page_size));
237            reader.seek(page_offset).map_err(binrw::Error::Io)?;
238            let page = Page::read_options(reader, endian, (self.page_size, db_type))?;
239            let is_last_page = &page.header.page_index == last_page;
240            page_index = page.header.next_page.clone();
241            pages.push(page);
242
243            if is_last_page {
244                break;
245            }
246        }
247        Ok(pages)
248    }
249}
250
251/// An entry in an index page.
252#[binrw]
253#[derive(PartialEq, Eq, Clone, Copy)]
254#[brw(little)]
255pub struct IndexEntry(u32);
256
257impl IndexEntry {
258    /// Size of the index entry in bytes.
259    pub const BINARY_SIZE: u32 = 4;
260}
261
262impl TryFrom<(PageIndex, u8)> for IndexEntry {
263    type Error = PdbError;
264
265    fn try_from(value: (PageIndex, u8)) -> Result<Self, Self::Error> {
266        let (page_index, index_flags) = value;
267        if index_flags & 0b111 != index_flags {
268            return Err(PdbError::InvalidIndexFlags(index_flags));
269        }
270        Ok(Self((page_index.0 << 3) | (index_flags & 0b111) as u32))
271    }
272}
273
274impl IndexEntry {
275    /// Returns bits 31-3 as a `PageIndex` which points to a page containing
276    /// data rows, with `page_flags=0x34` and same `page_type` as this page.
277    pub fn page_index(&self) -> Result<PageIndex, PdbError> {
278        PageIndex::try_from(self.0 >> 3)
279    }
280
281    /// Returns the index flags from bits 2-0. Their meaning is currently
282    /// unknown.
283    #[must_use]
284    pub fn index_flags(&self) -> u8 {
285        (self.0 & 0b111) as u8
286    }
287
288    /// Returns `true` if the entry is an empty slot.
289    #[must_use]
290    pub fn is_empty(&self) -> bool {
291        self.0 == 0x1FFF_FFF8
292    }
293
294    /// Creates a new empty `IndexEntry`.
295    #[must_use]
296    pub const fn empty() -> Self {
297        Self(0x1FFF_FFF8)
298    }
299}
300
301impl fmt::Debug for IndexEntry {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        if self.is_empty() {
304            f.debug_struct("IndexEntry")
305                .field("is_empty", &self.is_empty())
306                .finish()
307        } else {
308            f.debug_struct("IndexEntry")
309                .field("is_empty", &self.is_empty())
310                .field("page_index", &self.page_index().unwrap())
311                .field("index_flags", &self.index_flags())
312                .finish()
313        }
314    }
315}
316
317/// The header of the index-containing part of a page.
318#[binrw]
319#[derive(Debug, PartialEq, Eq, Clone)]
320pub struct IndexPageHeader {
321    /// Unknown field, usually `0x1fff` or `0x0001`.
322    pub unknown_a: u16,
323    /// Unknown field, usually `0x1fff` or `0x0000`.
324    pub unknown_b: u16,
325    // Magic value `0x03ec`.
326    #[brw(magic = 0x03ecu16)]
327    /// Offset where the next index entry will be written from the beginning
328    /// of the entries array, i.e. if this is 4 it means the next entry should
329    /// be written at byte `entries+4*4`. We still do not know why this value
330    /// is sometimes different than num_entries.
331    pub next_offset: u16,
332    /// Redundant page index.
333    pub page_index: PageIndex,
334    /// Redundant next page index.
335    pub next_page: PageIndex,
336    // Magic value `0x0000000003ffffff`.
337    #[brw(magic = 0x0000_0000_03ff_ffffu64)]
338    /// Number of index entries in this page.
339    pub num_entries: u16,
340    /// Points to the first empty index entry, or `0x1fff` if none.
341    ///
342    /// In real databases, this has been found to be one of three things:
343    /// 1. The same value as `num_entries`.
344    /// 2. `0x1fff`. We assume this has the same meaning as **1.**
345    /// 3. A number smaller than `num_entries`, indicating the first empty
346    /// slot.
347    pub first_empty: u16,
348}
349
350impl IndexPageHeader {
351    /// Size of the index page header in bytes.
352    pub const BINARY_SIZE: u32 = 28;
353}
354
355/// The content of an index page.
356#[binrw]
357#[derive(Debug, PartialEq, Eq, Clone)]
358#[br(little)]
359#[bw(little, import { page_size: u32 })]
360pub struct IndexPageContent {
361    /// The header of the index page.
362    pub header: IndexPageHeader,
363
364    /// The index entries.
365    #[br(count = header.num_entries)]
366    pub entries: Vec<IndexEntry>,
367
368    // Write empty entries to pad out the rest of the page, except the last
369    // 20 bytes which are zeros instead.
370    #[br(temp)]
371    #[bw(calc = EmptyIndexEntries(
372        Self::total_entries(page_size) - usize::from(header.num_entries)
373    ))]
374    #[bw(pad_after = 20)]
375    _empty_entries: EmptyIndexEntries,
376}
377
378impl IndexPageContent {
379    fn total_entries(page_size: u32) -> usize {
380        // The last 20 bytes in an index page are zeros.
381        let entries_space = page_size - PageHeader::BINARY_SIZE - IndexPageHeader::BINARY_SIZE - 20;
382        (entries_space / IndexEntry::BINARY_SIZE)
383            .try_into()
384            .unwrap()
385    }
386}
387
388/// Helper struct to write empty index entries while reading nothing.
389struct EmptyIndexEntries(usize);
390
391impl BinRead for EmptyIndexEntries {
392    type Args<'a> = ();
393
394    fn read_options<Reader>(_: &mut Reader, _: Endian, (): Self::Args<'_>) -> BinResult<Self>
395    where
396        Reader: Read + Seek,
397    {
398        Ok(Self(0))
399    }
400}
401
402impl BinWrite for EmptyIndexEntries {
403    type Args<'a> = ();
404
405    fn write_options<Writer>(
406        &self,
407        writer: &mut Writer,
408        endian: Endian,
409        (): Self::Args<'_>,
410    ) -> BinResult<()>
411    where
412        Writer: Write + Seek,
413    {
414        const EMPTY: IndexEntry = IndexEntry::empty();
415        for _ in 0..self.0 {
416            EMPTY.write_options(writer, endian, ())?;
417        }
418        Ok(())
419    }
420}
421
422/// The content of a page, which can be of different types.
423///
424/// Does not implement `Eq` due to the `Unknown` variant.
425#[binrw]
426#[derive(Debug, PartialEq, Clone)]
427#[br(little, import { page_size: u32, header: &PageHeader })]
428#[bw(little, import { page_size: u32 })]
429pub enum PageContent {
430    /// The page contains data rows.
431    #[br(pre_assert(!header.page_flags.is_index_page()))]
432    Data(
433        #[br(args { page_size, page_header: header })]
434        #[bw(args { page_size })]
435        DataPageContent,
436    ),
437    /// The page is an index page.
438    #[br(pre_assert(header.page_flags.is_index_page()))]
439    Index(#[bw(args { page_size })] IndexPageContent),
440    /// The page is of an unknown or unsupported format.
441    Unknown,
442}
443
444impl PageContent {
445    /// Returns the data content of the page if it is a data page.
446    #[must_use]
447    pub fn into_data(self) -> Option<DataPageContent> {
448        match self {
449            PageContent::Data(data) => Some(data),
450            _ => None,
451        }
452    }
453
454    /// Returns the index content of the page if it is an index page.
455    #[must_use]
456    pub fn into_index(self) -> Option<IndexPageContent> {
457        match self {
458            PageContent::Index(index) => Some(index),
459            _ => None,
460        }
461    }
462}
463
464/// The header of a page.
465#[binrw]
466#[derive(Debug, PartialEq, Eq, Clone)]
467#[brw(little)]
468#[br(import(db_type: DatabaseType))]
469pub struct PageHeader {
470    // Magic signature for pages (must be 0).
471    #[brw(magic = 0u32)]
472    /// Index of the page.
473    ///
474    /// Should match the index used for lookup and can be used to verify that the correct page was loaded.
475    pub page_index: PageIndex,
476    /// Type of information that the rows of this page contain.
477    ///
478    /// Should match the page type of the table that this page belongs to.
479    #[br(args(db_type))]
480    pub page_type: PageType,
481    /// Index of the next page with the same page type.
482    ///
483    /// If this page is the last one of that type, the page index stored in the field will point
484    /// past the end of the file.
485    pub next_page: PageIndex,
486    /// Unknown field.
487    /// Appears to be a number between 1 and ~2500.
488    pub unknown1: u32,
489    /// Unknown field.
490    /// Appears to always be zero.
491    pub unknown2: u32,
492    /// Packed field containing:
493    /// - number of used row offsets in the page (13 bits).
494    /// - number of valid rows in the page (11 bits).
495    pub packed_row_counts: PackedRowCounts,
496    /// Page flags.
497    ///
498    /// According to [@flesniak](https://github.com/flesniak):
499    /// > strange pages: 0x44, 0x64; otherwise seen: 0x24, 0x34
500    pub page_flags: PageFlags,
501    /// Free space in bytes in the data section of the page (excluding the row offsets in the page footer).
502    pub free_size: u16,
503    /// Used space in bytes in the data section of the page.
504    pub used_size: u16,
505}
506
507impl PageHeader {
508    /// Size of the page header in bytes.
509    pub const BINARY_SIZE: u32 = 0x20;
510}
511
512/// A table page.
513///
514/// Each page consists of a header that contains information about the type, number of rows, etc.,
515/// followed by the data section that holds the row data. Each row needs to be located using an
516/// offset found in the page footer at the end of the page.
517#[binrw]
518#[derive(Debug, PartialEq)]
519#[brw(little)]
520#[br(import(page_size: u32, db_type: DatabaseType))]
521#[bw(import(page_size: u32))]
522pub struct Page {
523    /// The page header.
524    #[br(args(db_type))]
525    pub header: PageHeader,
526
527    /// The content of the page.
528    #[br(args { page_size, header: &header })]
529    #[bw(args { page_size })]
530    pub content: PageContent,
531}
532
533impl Page {
534    /// Allocate space for a new row in the page heap and return a function to
535    /// insert the row at the allocated offset. Returns `None` if there is
536    /// insufficient free space in the page.
537    ///
538    /// We do this allocate-then-insert dance so that we only take ownership of
539    /// the Row once we know we can insert it, avoiding unnecessary copies.
540    pub fn allocate_row<'a>(&'a mut self, bytes: u16) -> Option<impl FnOnce(Row) + 'a> {
541        match self.content {
542            PageContent::Index(_) | PageContent::Unknown => None,
543            PageContent::Data(ref mut dpc) => {
544                // Always align rows to 4 bytes.
545                let bytes = bytes.next_multiple_of(4);
546
547                // Assume the upper bound of required space.
548                let required_bytes = bytes + RowGroup::HEADER_SIZE + RowGroup::OFFSET_SIZE;
549                if self.header.free_size < required_bytes {
550                    return None;
551                }
552
553                let offset = self.header.used_size;
554                self.header.used_size += bytes;
555                self.header.free_size -= bytes;
556                let row_counts = &mut self.header.packed_row_counts;
557                row_counts.increment_num_rows();
558                let (row_group_index, row_subindex) = row_counts.last_row_index().unwrap();
559
560                if dpc.row_groups.get(row_group_index as usize).is_none() {
561                    dpc.row_groups.push(RowGroup::empty());
562                    self.header.free_size -= RowGroup::HEADER_SIZE;
563                }
564
565                let row_group = dpc.row_groups.get_mut(row_group_index as usize).unwrap();
566                row_group.insert_offset(row_subindex, offset);
567                self.header.free_size -= RowGroup::OFFSET_SIZE;
568
569                let rows = &mut dpc.rows;
570
571                Some(move |row: Row| {
572                    let prev_entry = rows.insert(offset, row);
573                    if prev_entry.is_some() {
574                        panic!(
575                            "Offset {} was already occupied by row {:?}",
576                            offset,
577                            prev_entry.unwrap()
578                        );
579                    }
580                    row_group.mark_offset_present(row_subindex);
581                    row_counts.increment_num_rows_valid();
582                })
583            }
584        }
585    }
586}
587
588/// The header of the data-containing part of a page.
589#[binrw]
590#[derive(Debug, PartialEq, Eq, Clone)]
591pub struct DataPageHeader {
592    /// Unknown field.
593    /// Often 1 or 0x1fff; also observed: 8, 27, 22, 17, 2.
594    ///
595    /// According to [@flesniak](https://github.com/flesniak):
596    /// > (0->1: 2)
597    pub unknown5: u16,
598    /// Unknown field related to the number of rows in the table,
599    /// but not equal to it.
600    pub unknown_not_num_rows_large: u16,
601    /// Unknown field (usually zero).
602    pub unknown6: u16,
603    /// Unknown field (usually zero).
604    ///
605    /// According to [@flesniak](https://github.com/flesniak):
606    /// > always 0, except 1 for history pages, num entries for strange pages?"
607    /// @RobinMcCorkell: I don't think this is correct, my DB only has zeros for all pages.
608    pub unknown7: u16,
609}
610
611impl DataPageHeader {
612    /// Size of the page header in bytes.
613    pub const BINARY_SIZE: u32 = 0x8;
614}
615
616/// The data-containing part of a page.
617#[binrw]
618#[derive(Debug, PartialEq, Eq, Clone)]
619#[br(little, import { page_size: u32, page_header: &PageHeader })]
620#[bw(little, import { page_size: u32 })]
621pub struct DataPageContent {
622    /// The header of the data page.
623    pub header: DataPageHeader,
624
625    /// Row groups at the end of the page.
626    ///
627    /// Seek to the end of the page as we read/write row groups backwards,
628    /// but restore the position after to read/write the actual rows.
629    #[brw(seek_before(SeekFrom::Current(Self::page_heap_size(page_size).into())), restore_position)]
630    #[br(args {count: page_header.packed_row_counts.num_row_groups().into()})]
631    pub row_groups: Vec<RowGroup>,
632
633    /// Rows belonging to this page by the heap offset at which each is stored.
634    ///
635    /// The offsets here should match those in `row_groups`.
636    #[br(args(page_header.page_type))]
637    #[br(parse_with = parse_at_offsets(row_groups.iter().flat_map(RowGroup::present_rows_offsets)))]
638    #[bw(write_with = write_at_offsets)]
639    #[br(assert(rows.len() == page_header.packed_row_counts.num_rows_valid().into(), "parsing page {:?}: num_rows_valid {} does not match parsed row count {}", page_header.page_index, page_header.packed_row_counts.num_rows_valid(), rows.len()))]
640    pub rows: BTreeMap<u16, Row>,
641}
642
643impl DataPageContent {
644    fn page_heap_size(page_size: u32) -> u32 {
645        page_size - PageHeader::BINARY_SIZE - DataPageHeader::BINARY_SIZE
646    }
647}
648
649// Usage of PageHeapObject is coming in a future PR.
650#[allow(dead_code)]
651trait PageHeapObject {
652    type Args<'a>;
653
654    /// Required page heap space in bytes to store the object.
655    fn heap_bytes_required(&self, args: Self::Args<'_>) -> u16;
656}
657
658impl PageHeapObject for u8 {
659    type Args<'a> = ();
660    fn heap_bytes_required(&self, _: ()) -> u16 {
661        std::mem::size_of::<u8>() as u16
662    }
663}
664
665impl PageHeapObject for u16 {
666    type Args<'a> = ();
667    fn heap_bytes_required(&self, _: ()) -> u16 {
668        std::mem::size_of::<u16>() as u16
669    }
670}
671
672impl PageHeapObject for u32 {
673    type Args<'a> = ();
674    fn heap_bytes_required(&self, _: ()) -> u16 {
675        std::mem::size_of::<u32>() as u16
676    }
677}
678
679impl PageHeapObject for ColorIndex {
680    type Args<'a> = ();
681    fn heap_bytes_required(&self, _: ()) -> u16 {
682        (0u8).heap_bytes_required(())
683    }
684}
685
686impl PageHeapObject for FileType {
687    type Args<'a> = ();
688    fn heap_bytes_required(&self, _: ()) -> u16 {
689        (0u16).heap_bytes_required(())
690    }
691}
692
693/// A group of row indices, which are built backwards from the end of the page. Holds up to sixteen
694/// row offsets, along with a bit mask that indicates whether each row is actually present in the
695/// table.
696#[binrw]
697#[derive(Debug, Clone, Eq)]
698pub struct RowGroup {
699    /// An offset which points to a row in the table, whose actual presence is controlled by one of the
700    /// bits in `row_present_flags`. This instance allows the row itself to be lazily loaded, unless it
701    /// is not present, in which case there is no content to be loaded.
702    ///
703    /// Row groups are read backwards so first seek backwards.
704    ///
705    /// **Note:** Offsets are filled from the end and may only be partially present, i.e. earlier offsets
706    /// may be "uninitialized" and used as part of the page heap instead. We only start writing offsets
707    /// from the first present row to avoid clobbering page heap data.
708    #[brw(seek_before = SeekFrom::Current(-i64::from(Self::BINARY_SIZE)))]
709    #[bw(write_with = Self::write_row_offsets, args(*row_presence_flags))]
710    pub row_offsets: [u16; Self::MAX_ROW_COUNT],
711    /// A bit mask that indicates which rows in this group are actually present.
712    pub row_presence_flags: u16,
713    /// Unknown field.
714    /// Often zero, sometimes a multiple of 2, rarely something else.
715    /// When a multiple of 2, the set bit often aligns with the last present row
716    /// in the group, so maybe this is a bitset like the flags.
717    ///
718    /// E.g. for a full Artist rowgroup, this is usually zero.
719    /// For the last Artist rowgroup in the page with flags 0x003f, this is often 0x0020.
720    pub unknown: u16,
721
722    // Seek to the start of the row group to prepare for reading the next one.
723    #[br(temp)]
724    #[bw(calc = ())]
725    #[brw(seek_before = SeekFrom::Current(-i64::from(Self::BINARY_SIZE)))]
726    _dummy: (),
727}
728
729impl RowGroup {
730    const MAX_ROW_COUNT: usize = 16;
731    const HEADER_SIZE: u16 = 4; // row_presence_flags and unknown fields.
732    const OFFSET_SIZE: u16 = 2;
733    const BINARY_SIZE: u16 = (Self::MAX_ROW_COUNT as u16) * Self::OFFSET_SIZE + Self::HEADER_SIZE;
734
735    fn empty() -> Self {
736        Self {
737            row_offsets: [0; Self::MAX_ROW_COUNT],
738            row_presence_flags: 0,
739            unknown: 0,
740        }
741    }
742
743    fn present_rows_offsets(&self) -> impl Iterator<Item = u16> + '_ {
744        self.row_offsets
745            .iter()
746            .rev()
747            .enumerate()
748            .filter_map(move |(i, row_offset)| {
749                (self.row_presence_flags & (1 << i) != 0).then_some(*row_offset)
750            })
751    }
752
753    fn write_row_offsets<Writer>(
754        row_offsets: &[u16; 16],
755        writer: &mut Writer,
756        endian: Endian,
757        (row_presence_flags,): (u16,),
758    ) -> BinResult<()>
759    where
760        Writer: Write + Seek,
761    {
762        const U16_SIZE: u32 = std::mem::size_of::<u16>() as u32;
763        let skip = row_presence_flags.leading_zeros();
764        writer.seek(SeekFrom::Current((skip * U16_SIZE).into()))?;
765        for offset in row_offsets.iter().skip(skip.try_into().unwrap()) {
766            offset.write_options(writer, endian, ())?;
767        }
768        Ok(())
769    }
770
771    /// Insert a row offset into the group at the given subindex
772    /// but do not mark it present yet (see `mark_offset_present`).
773    fn insert_offset(&mut self, subindex: u16, offset: u16) {
774        self.row_offsets[(Self::MAX_ROW_COUNT as u16 - 1 - subindex) as usize] = offset;
775    }
776
777    /// Mark an inserted row offset as present.
778    fn mark_offset_present(&mut self, subindex: u16) {
779        self.row_presence_flags |= 1 << subindex;
780    }
781}
782
783impl Default for RowGroup {
784    fn default() -> Self {
785        Self::empty()
786    }
787}
788
789impl PartialEq for RowGroup {
790    fn eq(&self, other: &Self) -> bool {
791        self.unknown == other.unknown
792            && self.present_rows_offsets().eq(other.present_rows_offsets())
793    }
794}
795
796/// Carries additional information about a row (if present, always as the first field of a row)
797#[binrw]
798#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
799#[brw(little)]
800pub struct Subtype(pub u16);
801
802impl PageHeapObject for Subtype {
803    type Args<'a> = ();
804    fn heap_bytes_required(&self, _: ()) -> u16 {
805        self.0.heap_bytes_required(())
806    }
807}
808
809impl Subtype {
810    /// Returns the offset size (`OffsetSize`) used for this subtype.
811    ///
812    /// If the 0x04 bit is not set in the subtype, returns `OffsetSize::U8`,
813    /// otherwise returns `OffsetSize::U16`.
814    #[must_use]
815    pub fn get_offset_size(&self) -> OffsetSize {
816        if self.0 & 0x04 == 0 {
817            OffsetSize::U8
818        } else {
819            OffsetSize::U16
820        }
821    }
822}
823
824/// Identifies a track.
825#[binrw]
826#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
827#[brw(little)]
828pub struct TrackId(pub u32);
829
830impl PageHeapObject for TrackId {
831    type Args<'a> = ();
832    fn heap_bytes_required(&self, _: ()) -> u16 {
833        self.0.heap_bytes_required(())
834    }
835}
836
837/// Identifies an artwork item.
838#[binrw]
839#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
840#[brw(little)]
841pub struct ArtworkId(pub u32);
842
843impl PageHeapObject for ArtworkId {
844    type Args<'a> = ();
845    fn heap_bytes_required(&self, _: ()) -> u16 {
846        self.0.heap_bytes_required(())
847    }
848}
849
850/// Identifies an album.
851#[binrw]
852#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
853#[brw(little)]
854pub struct AlbumId(pub u32);
855
856impl PageHeapObject for AlbumId {
857    type Args<'a> = ();
858    fn heap_bytes_required(&self, _: ()) -> u16 {
859        self.0.heap_bytes_required(())
860    }
861}
862
863/// Identifies an artist.
864#[binrw]
865#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
866#[brw(little)]
867pub struct ArtistId(pub u32);
868
869impl PageHeapObject for ArtistId {
870    type Args<'a> = ();
871    fn heap_bytes_required(&self, _: ()) -> u16 {
872        self.0.heap_bytes_required(())
873    }
874}
875
876/// Identifies a genre.
877#[binrw]
878#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
879#[brw(little)]
880pub struct GenreId(pub u32);
881
882impl PageHeapObject for GenreId {
883    type Args<'a> = ();
884    fn heap_bytes_required(&self, _: ()) -> u16 {
885        self.0.heap_bytes_required(())
886    }
887}
888
889/// Identifies a key.
890#[binrw]
891#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
892#[brw(little)]
893pub struct KeyId(pub u32);
894
895impl PageHeapObject for KeyId {
896    type Args<'a> = ();
897    fn heap_bytes_required(&self, _: ()) -> u16 {
898        self.0.heap_bytes_required(())
899    }
900}
901
902/// Identifies a label.
903#[binrw]
904#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
905#[brw(little)]
906pub struct LabelId(pub u32);
907
908impl PageHeapObject for LabelId {
909    type Args<'a> = ();
910    fn heap_bytes_required(&self, _: ()) -> u16 {
911        self.0.heap_bytes_required(())
912    }
913}
914
915/// Identifies a playlist tree node.
916#[binrw]
917#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
918#[brw(little)]
919pub struct PlaylistTreeNodeId(pub u32);
920
921impl PageHeapObject for PlaylistTreeNodeId {
922    type Args<'a> = ();
923    fn heap_bytes_required(&self, _: ()) -> u16 {
924        self.0.heap_bytes_required(())
925    }
926}
927
928/// Identifies a history playlist.
929#[binrw]
930#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
931#[brw(little)]
932pub struct HistoryPlaylistId(pub u32);
933
934impl PageHeapObject for HistoryPlaylistId {
935    type Args<'a> = ();
936    fn heap_bytes_required(&self, _: ()) -> u16 {
937        self.0.heap_bytes_required(())
938    }
939}
940
941#[derive(Debug, PartialEq, Clone, Eq)]
942/// Represents a trailing name field at the end of a row, used for album and artist names.
943pub struct TrailingName {
944    /// The name a the end of the row this is used in
945    pub name: DeviceSQLString,
946}
947
948impl OffsetArrayItems<1> for TrailingName {
949    type Item = DeviceSQLString;
950
951    fn as_items(&self) -> [&Self::Item; 1] {
952        [&self.name]
953    }
954
955    fn from_items(items: [Self::Item; 1]) -> Self {
956        let [name] = items;
957        Self { name }
958    }
959}
960
961/// Contains the album name, along with an ID of the corresponding artist.
962#[binrw]
963#[derive(Debug, PartialEq, Eq, Clone)]
964#[brw(little)]
965pub struct Album {
966    /// Unknown field, usually `80 00`.
967    subtype: Subtype,
968    /// Unknown field, called `index_shift` by [@flesniak](https://github.com/flesniak).
969    /// Appears to always be 0x20 * row index.
970    index_shift: u16,
971    /// Unknown field.
972    unknown2: u32,
973    /// ID of the artist row associated with this row.
974    artist_id: ArtistId,
975    /// ID of this row.
976    id: AlbumId,
977    /// Unknown field.
978    unknown3: u32,
979    /// The offsets and its data and the end of this row
980    #[brw(args(20, subtype.get_offset_size(), ()))]
981    offsets: OffsetArrayContainer<TrailingName, 1>,
982}
983
984impl PageHeapObject for Album {
985    type Args<'a> = ();
986    fn heap_bytes_required(&self, _: ()) -> u16 {
987        [
988            self.subtype.heap_bytes_required(()),
989            self.index_shift.heap_bytes_required(()),
990            self.unknown2.heap_bytes_required(()),
991            self.artist_id.heap_bytes_required(()),
992            self.id.heap_bytes_required(()),
993            self.unknown3.heap_bytes_required(()),
994            self.offsets
995                .heap_bytes_required(self.subtype.get_offset_size()),
996        ]
997        .iter()
998        .sum()
999    }
1000}
1001
1002/// Contains the artist name and ID.
1003#[binrw]
1004#[derive(Debug, PartialEq, Eq, Clone)]
1005#[brw(little)]
1006pub struct Artist {
1007    /// Determines if the `name` string is located at the 8-bit offset (0x60) or the 16-bit offset (0x64).
1008    subtype: Subtype,
1009    /// Unknown field, called `index_shift` by [@flesniak](https://github.com/flesniak).
1010    /// Appears to always be 0x20 * row index.
1011    index_shift: u16,
1012    /// ID of this row.
1013    pub id: ArtistId,
1014    /// offsets at the row end
1015    #[brw(args(8, subtype.get_offset_size(), ()))]
1016    pub offsets: OffsetArrayContainer<TrailingName, 1>,
1017}
1018
1019impl PageHeapObject for Artist {
1020    type Args<'a> = ();
1021    fn heap_bytes_required(&self, _: ()) -> u16 {
1022        [
1023            self.subtype.heap_bytes_required(()),
1024            self.index_shift.heap_bytes_required(()),
1025            self.id.heap_bytes_required(()),
1026            self.offsets
1027                .heap_bytes_required(self.subtype.get_offset_size()),
1028        ]
1029        .iter()
1030        .sum()
1031    }
1032}
1033
1034/// Contains the artwork path and ID.
1035#[binrw]
1036#[derive(Debug, PartialEq, Eq, Clone)]
1037#[brw(little)]
1038pub struct Artwork {
1039    /// ID of this row.
1040    id: ArtworkId,
1041    /// Path to the album art file.
1042    path: DeviceSQLString,
1043}
1044
1045impl PageHeapObject for Artwork {
1046    type Args<'a> = ();
1047    fn heap_bytes_required(&self, _: ()) -> u16 {
1048        [
1049            self.id.heap_bytes_required(()),
1050            self.path.heap_bytes_required(()),
1051        ]
1052        .iter()
1053        .sum()
1054    }
1055}
1056
1057/// Contains numeric color ID
1058#[binrw]
1059#[derive(Debug, PartialEq, Eq, Clone)]
1060#[brw(little)]
1061pub struct Color {
1062    /// Unknown field.
1063    unknown1: u32,
1064    /// Unknown field.
1065    unknown2: u8,
1066    /// Numeric color ID
1067    color: ColorIndex,
1068    /// Unknown field.
1069    unknown3: u16,
1070    /// User-defined name of the color.
1071    name: DeviceSQLString,
1072}
1073
1074impl PageHeapObject for Color {
1075    type Args<'a> = ();
1076    fn heap_bytes_required(&self, _: ()) -> u16 {
1077        [
1078            self.unknown1.heap_bytes_required(()),
1079            self.unknown2.heap_bytes_required(()),
1080            self.color.heap_bytes_required(()),
1081            self.unknown3.heap_bytes_required(()),
1082            self.name.heap_bytes_required(()),
1083        ]
1084        .iter()
1085        .sum()
1086    }
1087}
1088
1089/// Represents a musical genre.
1090#[binrw]
1091#[derive(Debug, PartialEq, Eq, Clone)]
1092#[brw(little)]
1093pub struct Genre {
1094    /// ID of this row.
1095    id: GenreId,
1096    /// Name of the genre.
1097    name: DeviceSQLString,
1098}
1099
1100impl PageHeapObject for Genre {
1101    type Args<'a> = ();
1102    fn heap_bytes_required(&self, _: ()) -> u16 {
1103        [
1104            self.id.heap_bytes_required(()),
1105            self.name.heap_bytes_required(()),
1106        ]
1107        .iter()
1108        .sum()
1109    }
1110}
1111
1112/// Represents a history playlist.
1113#[binrw]
1114#[derive(Debug, PartialEq, Eq, Clone)]
1115#[brw(little)]
1116pub struct HistoryPlaylist {
1117    /// ID of this row.
1118    id: HistoryPlaylistId,
1119    /// Name of the playlist.
1120    name: DeviceSQLString,
1121}
1122
1123impl PageHeapObject for HistoryPlaylist {
1124    type Args<'a> = ();
1125    fn heap_bytes_required(&self, _: ()) -> u16 {
1126        [
1127            self.id.heap_bytes_required(()),
1128            self.name.heap_bytes_required(()),
1129        ]
1130        .iter()
1131        .sum()
1132    }
1133}
1134
1135/// Represents a history playlist.
1136#[binrw]
1137#[derive(Debug, PartialEq, Eq, Clone)]
1138#[brw(little)]
1139pub struct HistoryEntry {
1140    /// ID of the track played at this position in the playlist.
1141    track_id: TrackId,
1142    /// ID of the history playlist.
1143    playlist_id: HistoryPlaylistId,
1144    /// Position within the playlist.
1145    entry_index: u32,
1146}
1147
1148impl PageHeapObject for HistoryEntry {
1149    type Args<'a> = ();
1150    fn heap_bytes_required(&self, _: ()) -> u16 {
1151        [
1152            self.track_id.heap_bytes_required(()),
1153            self.playlist_id.heap_bytes_required(()),
1154            self.entry_index.heap_bytes_required(()),
1155        ]
1156        .iter()
1157        .sum()
1158    }
1159}
1160
1161/// Represents a musical key.
1162#[binrw]
1163#[derive(Debug, PartialEq, Eq, Clone)]
1164#[brw(little)]
1165pub struct Key {
1166    /// ID of this row.
1167    id: KeyId,
1168    /// Apparently a second copy of the row ID.
1169    id2: u32,
1170    /// Name of the key.
1171    name: DeviceSQLString,
1172}
1173
1174impl PageHeapObject for Key {
1175    type Args<'a> = ();
1176    fn heap_bytes_required(&self, _: ()) -> u16 {
1177        [
1178            self.id.heap_bytes_required(()),
1179            self.id2.heap_bytes_required(()),
1180            self.name.heap_bytes_required(()),
1181        ]
1182        .iter()
1183        .sum()
1184    }
1185}
1186
1187/// Represents a record label.
1188#[binrw]
1189#[derive(Debug, PartialEq, Eq, Clone)]
1190#[brw(little)]
1191pub struct Label {
1192    /// ID of this row.
1193    id: LabelId,
1194    /// Name of the record label.
1195    name: DeviceSQLString,
1196}
1197
1198impl PageHeapObject for Label {
1199    type Args<'a> = ();
1200    fn heap_bytes_required(&self, _: ()) -> u16 {
1201        [
1202            self.id.heap_bytes_required(()),
1203            self.name.heap_bytes_required(()),
1204        ]
1205        .iter()
1206        .sum()
1207    }
1208}
1209
1210/// Represents a node in the playlist tree (either a folder or a playlist).
1211#[binrw]
1212#[derive(Debug, PartialEq, Eq, Clone)]
1213#[brw(little)]
1214pub struct PlaylistTreeNode {
1215    /// ID of parent row of this row (which means that the parent is a folder).
1216    pub parent_id: PlaylistTreeNodeId,
1217    /// Unknown field.
1218    unknown: u32,
1219    /// Sort order indicastor.
1220    sort_order: u32,
1221    /// ID of this row.
1222    pub id: PlaylistTreeNodeId,
1223    /// Indicates if the node is a folder. Non-zero if it's a leaf node, i.e. a playlist.
1224    node_is_folder: u32,
1225    /// Name of this node, as shown when navigating the menu.
1226    pub name: DeviceSQLString,
1227}
1228
1229impl PlaylistTreeNode {
1230    /// Indicates whether the node is a folder or a playlist.
1231    #[must_use]
1232    pub fn is_folder(&self) -> bool {
1233        self.node_is_folder > 0
1234    }
1235}
1236
1237impl PageHeapObject for PlaylistTreeNode {
1238    type Args<'a> = ();
1239    fn heap_bytes_required(&self, _: ()) -> u16 {
1240        [
1241            self.parent_id.heap_bytes_required(()),
1242            self.unknown.heap_bytes_required(()),
1243            self.sort_order.heap_bytes_required(()),
1244            self.id.heap_bytes_required(()),
1245            self.node_is_folder.heap_bytes_required(()),
1246            self.name.heap_bytes_required(()),
1247        ]
1248        .iter()
1249        .sum()
1250    }
1251}
1252
1253/// Represents a track entry in a playlist.
1254#[binrw]
1255#[derive(Debug, PartialEq, Eq, Clone)]
1256#[brw(little)]
1257pub struct PlaylistEntry {
1258    /// Position within the playlist.
1259    pub entry_index: u32,
1260    /// ID of the track played at this position in the playlist.
1261    pub track_id: TrackId,
1262    /// ID of the playlist.
1263    pub playlist_id: PlaylistTreeNodeId,
1264}
1265
1266impl PageHeapObject for PlaylistEntry {
1267    type Args<'a> = ();
1268    fn heap_bytes_required(&self, _: ()) -> u16 {
1269        [
1270            self.entry_index.heap_bytes_required(()),
1271            self.track_id.heap_bytes_required(()),
1272            self.playlist_id.heap_bytes_required(()),
1273        ]
1274        .iter()
1275        .sum()
1276    }
1277}
1278
1279/// Contains the kinds of Metadata Categories tracks can be browsed by
1280/// on CDJs.
1281#[binrw]
1282#[derive(Debug, PartialEq, Eq, Clone)]
1283#[brw(little)]
1284pub struct ColumnEntry {
1285    // Possibly the primary key, though I don't know if that would
1286    // make sense as I don't think there are references to these
1287    // rows anywhere else. This could be a stable ID to identify
1288    // a category by in hardware (instead of by name).
1289    id: u16,
1290    // Maybe a bitfield containing infos on sort order and which
1291    // columns are displayed.
1292    unknown0: u16,
1293    /// TODO Contained string is prefixed by the "interlinear annotation"
1294    /// characters "\u{fffa}" and postfixed with "\u{fffb}" for some reason?!
1295    /// Contained strings are actually `DeviceSQLString::LongBody` even though
1296    /// they only contain ascii (apart from their unicode annotations)
1297    // TODO since there are only finite many categories, it would make sense
1298    // to encode those as an enum as part of the high-level api.
1299    pub column_name: DeviceSQLString,
1300}
1301
1302impl PageHeapObject for ColumnEntry {
1303    type Args<'a> = ();
1304    fn heap_bytes_required(&self, _: ()) -> u16 {
1305        [
1306            self.id.heap_bytes_required(()),
1307            self.unknown0.heap_bytes_required(()),
1308            self.column_name.heap_bytes_required(()),
1309        ]
1310        .iter()
1311        .sum()
1312    }
1313}
1314
1315#[derive(Debug, PartialEq, Clone, Eq)]
1316/// String fields stored via the offset table in Track rows
1317pub struct TrackStrings {
1318    /// International Standard Recording Code (ISRC), in mangled format.
1319    isrc: DeviceSQLString,
1320    /// Lyricist of the track.
1321    lyricist: DeviceSQLString,
1322    /// Unknown string field containing a number.
1323    /// Appears to increment when the track is exported or modified in Rekordbox.
1324    unknown_string2: DeviceSQLString,
1325    /// Unknown string field containing a number.
1326    unknown_string3: DeviceSQLString,
1327    /// Unknown string field.
1328    unknown_string4: DeviceSQLString,
1329    /// Track "message", a field in the Rekordbox UI.
1330    message: DeviceSQLString,
1331    /// "Publish track information" in Rekordbox, value is either "ON" or empty string.
1332    /// Appears related to the Stagehand product to control DJ equipment remotely.
1333    publish_track_information: DeviceSQLString,
1334    /// Determines if hotcues should be autoloaded. Value is either "ON" or empty string.
1335    autoload_hotcues: DeviceSQLString,
1336    /// Unknown string field (usually empty).
1337    unknown_string5: DeviceSQLString,
1338    /// Unknown string field (usually empty).
1339    unknown_string6: DeviceSQLString,
1340    /// Date when the track was added to the Rekordbox collection (YYYY-MM-DD).
1341    date_added: DeviceSQLString,
1342    /// Date when the track was released (YYYY-MM-DD).
1343    release_date: DeviceSQLString,
1344    /// Name of the remix (if any).
1345    mix_name: DeviceSQLString,
1346    /// Unknown string field (usually empty).
1347    unknown_string7: DeviceSQLString,
1348    /// File path of the track analysis file.
1349    analyze_path: DeviceSQLString,
1350    /// Date when the track analysis was performed (YYYY-MM-DD).
1351    analyze_date: DeviceSQLString,
1352    /// Track comment.
1353    comment: DeviceSQLString,
1354    /// Track title.
1355    pub title: DeviceSQLString,
1356    /// Unknown string field (usually empty).
1357    unknown_string8: DeviceSQLString,
1358    /// Name of the file.
1359    filename: DeviceSQLString,
1360    /// Path of the file.
1361    pub file_path: DeviceSQLString,
1362}
1363
1364impl OffsetArrayItems<21> for TrackStrings {
1365    type Item = DeviceSQLString;
1366
1367    fn as_items(&self) -> [&Self::Item; 21] {
1368        [
1369            &self.isrc,
1370            &self.lyricist,
1371            &self.unknown_string2,
1372            &self.unknown_string3,
1373            &self.unknown_string4,
1374            &self.message,
1375            &self.publish_track_information,
1376            &self.autoload_hotcues,
1377            &self.unknown_string5,
1378            &self.unknown_string6,
1379            &self.date_added,
1380            &self.release_date,
1381            &self.mix_name,
1382            &self.unknown_string7,
1383            &self.analyze_path,
1384            &self.analyze_date,
1385            &self.comment,
1386            &self.title,
1387            &self.unknown_string8,
1388            &self.filename,
1389            &self.file_path,
1390        ]
1391    }
1392
1393    fn from_items(items: [Self::Item; 21]) -> Self {
1394        let [isrc, lyricist, unknown_string2, unknown_string3, unknown_string4, message, publish_track_information, autoload_hotcues, unknown_string5, unknown_string6, date_added, release_date, mix_name, unknown_string7, analyze_path, analyze_date, comment, title, unknown_string8, filename, file_path] =
1395            items;
1396        Self {
1397            isrc,
1398            lyricist,
1399            unknown_string2,
1400            unknown_string3,
1401            unknown_string4,
1402            message,
1403            publish_track_information,
1404            autoload_hotcues,
1405            unknown_string5,
1406            unknown_string6,
1407            date_added,
1408            release_date,
1409            mix_name,
1410            unknown_string7,
1411            analyze_path,
1412            analyze_date,
1413            comment,
1414            title,
1415            unknown_string8,
1416            filename,
1417            file_path,
1418        }
1419    }
1420}
1421
1422/// Contains the album name, along with an ID of the corresponding artist.
1423#[binrw]
1424#[derive(Debug, PartialEq, Eq, Clone)]
1425#[brw(little)]
1426pub struct Track {
1427    /// Unknown field, usually `24 00`.
1428    subtype: Subtype,
1429    /// Unknown field, called `index_shift` by [@flesniak](https://github.com/flesniak).
1430    /// Appears to always be 0x20 * row index.
1431    index_shift: u16,
1432    /// Unknown field, called `bitmask` by [@flesniak](https://github.com/flesniak).
1433    /// Appears to always be 0x000c0700.
1434    bitmask: u32,
1435    /// Sample Rate in Hz.
1436    sample_rate: u32,
1437    /// Composer of this track as artist row ID (non-zero if set).
1438    composer_id: ArtistId,
1439    /// File size in bytes.
1440    file_size: u32,
1441    /// Unknown field; observed values are effectively random.
1442    unknown2: u32,
1443    /// Unknown field; observed values: 19048, 64128, 31844.
1444    /// Appears to be the same for all tracks in a given DB.
1445    unknown3: u16,
1446    /// Unknown field; observed values: 30967, 1511, 9043.
1447    /// Appears to be the same for all tracks in a given DB.
1448    unknown4: u16,
1449    /// Artwork row ID for the cover art (non-zero if set),
1450    artwork_id: ArtworkId,
1451    /// Key row ID for the cover art (non-zero if set).
1452    key_id: KeyId,
1453    /// Artist row ID of the original performer (non-zero if set).
1454    orig_artist_id: ArtistId,
1455    /// Label row ID of the original performer (non-zero if set).
1456    label_id: LabelId,
1457    /// Artist row ID of the remixer (non-zero if set).
1458    remixer_id: ArtistId,
1459    /// Bitrate of the track.
1460    bitrate: u32,
1461    /// Track number of the track.
1462    track_number: u32,
1463    /// Track tempo in centi-BPM (= 1/100 BPM).
1464    tempo: u32,
1465    /// Genre row ID for this track (non-zero if set).
1466    genre_id: GenreId,
1467    /// Album row ID for this track (non-zero if set).
1468    album_id: AlbumId,
1469    /// Artist row ID for this track (non-zero if set).
1470    pub artist_id: ArtistId,
1471    /// Row ID of this track (non-zero if set).
1472    pub id: TrackId,
1473    /// Disc number of this track (non-zero if set).
1474    disc_number: u16,
1475    /// Number of times this track was played.
1476    play_count: u16,
1477    /// Year this track was released.
1478    year: u16,
1479    /// Bits per sample of the track aduio file.
1480    sample_depth: u16,
1481    /// Playback duration of this track in seconds (at normal speed).
1482    duration: u16,
1483    /// Unknown field, apparently always "0x29".
1484    unknown5: u16,
1485    /// Color row ID for this track (non-zero if set).
1486    color: ColorIndex,
1487    /// User rating of this track (0 to 5 starts).
1488    rating: u8,
1489    /// Format of the file.
1490    file_type: FileType,
1491    /// offsets (strings) at row end
1492    #[brw(args(0x5C, subtype.get_offset_size(), ()))]
1493    pub offsets: OffsetArrayContainer<TrackStrings, 21>,
1494}
1495
1496impl PageHeapObject for Track {
1497    type Args<'a> = ();
1498    fn heap_bytes_required(&self, _: ()) -> u16 {
1499        [
1500            self.subtype.heap_bytes_required(()),
1501            self.index_shift.heap_bytes_required(()),
1502            self.bitmask.heap_bytes_required(()),
1503            self.sample_rate.heap_bytes_required(()),
1504            self.composer_id.heap_bytes_required(()),
1505            self.file_size.heap_bytes_required(()),
1506            self.unknown2.heap_bytes_required(()),
1507            self.unknown3.heap_bytes_required(()),
1508            self.unknown4.heap_bytes_required(()),
1509            self.artwork_id.heap_bytes_required(()),
1510            self.key_id.heap_bytes_required(()),
1511            self.orig_artist_id.heap_bytes_required(()),
1512            self.label_id.heap_bytes_required(()),
1513            self.remixer_id.heap_bytes_required(()),
1514            self.bitrate.heap_bytes_required(()),
1515            self.track_number.heap_bytes_required(()),
1516            self.tempo.heap_bytes_required(()),
1517            self.genre_id.heap_bytes_required(()),
1518            self.album_id.heap_bytes_required(()),
1519            self.artist_id.heap_bytes_required(()),
1520            self.id.heap_bytes_required(()),
1521            self.disc_number.heap_bytes_required(()),
1522            self.play_count.heap_bytes_required(()),
1523            self.year.heap_bytes_required(()),
1524            self.sample_depth.heap_bytes_required(()),
1525            self.duration.heap_bytes_required(()),
1526            self.unknown5.heap_bytes_required(()),
1527            self.color.heap_bytes_required(()),
1528            self.rating.heap_bytes_required(()),
1529            self.file_type.heap_bytes_required(()),
1530            self.offsets
1531                .heap_bytes_required(self.subtype.get_offset_size()),
1532        ]
1533        .iter()
1534        .sum()
1535    }
1536}
1537
1538/// Visibility state for a Menu on the CDJ.
1539#[binrw]
1540#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1541#[brw(little)]
1542pub enum MenuVisibility {
1543    /// The menu is visible.
1544    #[brw(magic = 0x00u8)]
1545    Visible,
1546    /// The menu is hidden.
1547    #[brw(magic = 0x01u8)]
1548    Hidden,
1549    /// Unknown visibility flag.
1550    Unknown(u8),
1551}
1552
1553impl PageHeapObject for MenuVisibility {
1554    type Args<'a> = ();
1555    fn heap_bytes_required(&self, _: ()) -> u16 {
1556        (0u8).heap_bytes_required(())
1557    }
1558}
1559
1560/// This table defines the active menus on the CDJ.
1561#[binrw]
1562#[derive(Debug, PartialEq, Eq, Clone)]
1563#[brw(little)]
1564pub struct Menu {
1565    /// Determines the Label (e.g. "ARTIST").
1566    /// Matches IDs in the COLUMN table.
1567    pub category_id: u16,
1568
1569    /// Points to the data source, i.e. the list of artists is 0x02.
1570    pub content_pointer: u16,
1571    /// Unknown field. Swapping values here appears to have no effect on CDJ-350 behavior.
1572    ///
1573    /// Some observed values:
1574    /// - 0x01: Track
1575    /// - 0x02: Artist
1576    /// - 0x03: Album
1577    /// - 0x05: BPM
1578    /// - 0x63 (99): Generic List (Playlist, Genre, Key, History)
1579    pub unknown: u8,
1580
1581    /// Visibility state of the menu item.
1582    ///
1583    /// Experiments confirmed that changing this from `Hidden` to `Visible` makes hidden menus
1584    /// (like Genre) appear, although some menus do not show in the CDJ-350 even when made
1585    /// visible here.
1586    pub visibility: MenuVisibility,
1587
1588    /// Visual position in the menu list.
1589    /// 0 is valid and places the item at the very top (if visible).
1590    pub sort_order: u16,
1591}
1592
1593impl PageHeapObject for Menu {
1594    type Args<'a> = ();
1595    fn heap_bytes_required(&self, _: ()) -> u16 {
1596        [
1597            self.category_id.heap_bytes_required(()),
1598            self.content_pointer.heap_bytes_required(()),
1599            self.unknown.heap_bytes_required(()),
1600            self.visibility.heap_bytes_required(()),
1601            self.sort_order.heap_bytes_required(()),
1602        ]
1603        .iter()
1604        .sum()
1605    }
1606}
1607
1608/// A table row contains the actual data.
1609#[binrw]
1610#[derive(Debug, PartialEq, Eq, Clone)]
1611#[brw(little)]
1612#[br(import(page_type: PlainPageType))]
1613// The large enum size is unfortunate, but since users of this library will probably use iterators
1614// to consume the results on demand, we can live with this. The alternative of using a `Box` would
1615// require a heap allocation per row, which is arguably worse. Hence, the warning is disabled for
1616// this enum.
1617#[allow(clippy::large_enum_variant)]
1618pub enum PlainRow {
1619    /// Contains the album name, along with an ID of the corresponding artist.
1620    ///
1621    /// Fresh album rows typically have a bit of padding, presumably to allow
1622    /// edits on DJ gear.
1623    #[br(pre_assert(page_type == PlainPageType::Albums))]
1624    Album(#[bw(align_after = 4)] Album),
1625    /// Contains the artist name and ID.
1626    ///
1627    /// Fresh artist rows typically have a bit of padding, presumably to allow
1628    /// edits on DJ gear.
1629    #[br(pre_assert(page_type == PlainPageType::Artists))]
1630    Artist(#[bw(align_after = 4)] Artist),
1631    /// Contains the artwork path and ID.
1632    #[br(pre_assert(page_type == PlainPageType::Artwork))]
1633    Artwork(#[bw(align_after = 4)] Artwork),
1634    /// Contains numeric color ID
1635    #[br(pre_assert(page_type == PlainPageType::Colors))]
1636    Color(#[bw(align_after = 4)] Color),
1637    /// Represents a musical genre.
1638    #[br(pre_assert(page_type == PlainPageType::Genres))]
1639    Genre(#[bw(align_after = 4)] Genre),
1640    /// Represents a history playlist.
1641    #[br(pre_assert(page_type == PlainPageType::HistoryPlaylists))]
1642    HistoryPlaylist(#[bw(align_after = 4)] HistoryPlaylist),
1643    /// Represents a history playlist.
1644    #[br(pre_assert(page_type == PlainPageType::HistoryEntries))]
1645    HistoryEntry(#[bw(align_after = 4)] HistoryEntry),
1646    /// Represents a musical key.
1647    #[br(pre_assert(page_type == PlainPageType::Keys))]
1648    Key(#[bw(align_after = 4)] Key),
1649    /// Represents a record label.
1650    #[br(pre_assert(page_type == PlainPageType::Labels))]
1651    Label(#[bw(align_after = 4)] Label),
1652    /// Represents a node in the playlist tree (either a folder or a playlist).
1653    #[br(pre_assert(page_type == PlainPageType::PlaylistTree))]
1654    PlaylistTreeNode(#[bw(align_after = 4)] PlaylistTreeNode),
1655    /// Represents a track entry in a playlist.
1656    #[br(pre_assert(page_type == PlainPageType::PlaylistEntries))]
1657    PlaylistEntry(#[bw(align_after = 4)] PlaylistEntry),
1658    /// Contains the metadata categories by which Tracks can be browsed by.
1659    #[br(pre_assert(page_type == PlainPageType::Columns))]
1660    ColumnEntry(#[bw(align_after = 4)] ColumnEntry),
1661    /// Manages the active menus on the CDJ.
1662    #[br(pre_assert(page_type == PlainPageType::Menu))]
1663    Menu(#[bw(align_after = 4)] Menu),
1664    /// Contains a track entry.
1665    ///
1666    /// Fresh track rows typically have a bit of padding, presumably to allow
1667    /// edits on DJ gear.
1668    #[br(pre_assert(page_type == PlainPageType::Tracks))]
1669    Track(#[bw(align_after = 4)] Track),
1670}
1671
1672impl PageHeapObject for PlainRow {
1673    type Args<'a> = ();
1674    fn heap_bytes_required(&self, _: ()) -> u16 {
1675        match self {
1676            PlainRow::Album(album) => album.heap_bytes_required(()),
1677            PlainRow::Artist(artist) => artist.heap_bytes_required(()),
1678            PlainRow::Artwork(artwork) => artwork.heap_bytes_required(()),
1679            PlainRow::Color(color) => color.heap_bytes_required(()),
1680            PlainRow::Genre(genre) => genre.heap_bytes_required(()),
1681            PlainRow::HistoryPlaylist(history_playlist) => history_playlist.heap_bytes_required(()),
1682            PlainRow::HistoryEntry(history_entry) => history_entry.heap_bytes_required(()),
1683            PlainRow::Key(key) => key.heap_bytes_required(()),
1684            PlainRow::Label(label) => label.heap_bytes_required(()),
1685            PlainRow::PlaylistTreeNode(playlist_tree_node) => {
1686                playlist_tree_node.heap_bytes_required(())
1687            }
1688            PlainRow::PlaylistEntry(playlist_entry) => playlist_entry.heap_bytes_required(()),
1689            PlainRow::ColumnEntry(column_entry) => column_entry.heap_bytes_required(()),
1690            PlainRow::Menu(menu) => menu.heap_bytes_required(()),
1691            PlainRow::Track(track) => track.heap_bytes_required(()),
1692        }
1693    }
1694}
1695
1696/// A table row contains the actual data.
1697#[binrw]
1698#[derive(Debug, PartialEq, Eq, Clone)]
1699#[brw(little)]
1700#[br(import(page_type: PageType))]
1701// The large enum size is unfortunate, but since users of this library will probably use iterators
1702// to consume the results on demand, we can live with this. The alternative of using a `Box` would
1703// require a heap allocation per row, which is arguably worse. Hence, the warning is disabled for
1704// this enum.
1705#[allow(clippy::large_enum_variant)]
1706pub enum Row {
1707    // TODO(Swiftb0y: come up with something prettier than the match hell below)
1708    #[br(pre_assert(matches!(page_type, PageType::Plain(_))))]
1709    /// A row in a "plain" database (export.pdb), which contains one of the known row types.
1710    Plain(
1711        #[br(args(match page_type {
1712            PageType::Plain(v) => v,
1713            _ => unreachable!("by above pre_assert")
1714        }))]
1715        PlainRow,
1716    ),
1717    #[br(pre_assert(matches!(page_type, PageType::Ext(_))))]
1718    /// A row in an "ext" database (exportExt.pdb), which contains extended track information.
1719    Ext(
1720        #[br(args(match page_type {
1721            PageType::Ext(v) => v,
1722            _ => unreachable!("by above pre_assert")
1723        }))]
1724        ExtRow,
1725    ),
1726    /// The row format (and also its size) is unknown, which means it can't be parsed.
1727    #[br(pre_assert(matches!(page_type, PageType::Plain(PlainPageType::History) | PageType::Unknown(_))))]
1728    Unknown,
1729}
1730
1731impl PageHeapObject for Row {
1732    type Args<'a> = ();
1733    fn heap_bytes_required(&self, _: ()) -> u16 {
1734        match self {
1735            Row::Plain(plain_row) => plain_row.heap_bytes_required(()),
1736            Row::Ext(ext_row) => ext_row.heap_bytes_required(()),
1737            Row::Unknown => panic!("Unable to determine required bytes for unknown row type"),
1738        }
1739    }
1740}