Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/pdb/bitfields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,22 @@ impl PackedRowCounts {
pub(crate) fn num_row_groups(&self) -> u16 {
self.num_rows().div_ceil(RowGroup::MAX_ROW_COUNT as u16)
}

/// Get the index of the last row group in the page and
/// the index of the last row in that row group.
pub(crate) fn last_row_index(&self) -> Option<(u16, u16)> {
let rgi = self.num_row_groups().checked_sub(1)?;
let rsi = self
.num_rows()
.checked_sub(1)
.map(|n| n % RowGroup::MAX_ROW_COUNT as u16)?;
Some((rgi, rsi))
}

/// Increment both number of rows and number of valid rows by one,
/// e.g. when we are adding a new row to the page.
pub(crate) fn increment_rows(&mut self) {
self.set_num_rows(self.num_rows() + 1);
self.set_num_rows_valid(self.num_rows_valid() + 1);
}
}
93 changes: 79 additions & 14 deletions src/pdb/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
//! - <https://github.com/henrybetts/Rekordbox-Decoding>
//! - <https://github.com/flesniak/python-prodj-link/tree/master/prodj/pdblib>

use crate::pdb::{DeviceSQLString, OffsetArray, OffsetArrayContainer, Subtype, TrackId};
use crate::pdb::{
offset_array::OffsetArrayItems, DeviceSQLString, OffsetArrayContainer, PageHeapObject, Subtype,
TrackId,
};
use binrw::binrw;
use std::num::NonZero;

Expand All @@ -29,6 +32,13 @@ use std::num::NonZero;
#[brw(little)]
pub struct TagId(pub u32);

impl PageHeapObject for TagId {
type Args<'a> = ();
fn heap_bytes_required(&self, _: ()) -> u16 {
self.0.heap_bytes_required(())
}
}

#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[brw(little)]
Expand All @@ -39,24 +49,36 @@ pub struct ParentId(
pub Option<NonZero<u32>>,
);

#[binrw]
#[brw(little)]
#[brw(import(base: i64, offsets: &OffsetArray<2>, args: ()))]
impl PageHeapObject for ParentId {
type Args<'a> = ();
fn heap_bytes_required(&self, _: ()) -> u16 {
let inner: u32 = self.0.map_or(0u32, |v| v.get());
inner.heap_bytes_required(())
}
}

#[derive(Debug, PartialEq, Clone, Eq)]
/// The strings associated with a tag or category.
pub struct TagOrCategoryStrings {
#[brw(args(base, args))]
#[br(parse_with = offsets.read_offset(0))]
#[bw(write_with = offsets.write_offset(0))]
/// The name of the tag or category.
pub name: DeviceSQLString,
#[brw(args(base, args))]
#[br(parse_with = offsets.read_offset(1))]
#[bw(write_with = offsets.write_offset(1))]
/// String with unknown purpose, often empty.
pub unknown: DeviceSQLString,
}

impl OffsetArrayItems<2> for TagOrCategoryStrings {
type Item = DeviceSQLString;

fn as_items(&self) -> [&Self::Item; 2] {
[&self.name, &self.unknown]
}

fn from_items(items: [Self::Item; 2]) -> Self {
let [name, unknown] = items;
Self { name, unknown }
}
}

/// A tag or category that can be assigned to tracks for the purpose of categorization.
///
/// ## References
Expand Down Expand Up @@ -87,12 +109,31 @@ pub struct TagOrCategory {
pub id: TagId,
/// Non-zero if this row represents a category rather than a tag.
pub raw_is_category: u32,
// Padded at the end by 11 bytes as observed
#[brw(args(0x1C, subtype.get_offset_size(), ()), pad_after = 11)]
/// The strings associated with this tag or category.
#[brw(args(0x1C, subtype.get_offset_size(), ()))]
pub offsets: OffsetArrayContainer<TagOrCategoryStrings, 2>,
}

impl PageHeapObject for TagOrCategory {
type Args<'a> = ();
fn heap_bytes_required(&self, _: ()) -> u16 {
[
self.subtype.heap_bytes_required(()),
self.index_shift.heap_bytes_required(()),
self.unknown1.heap_bytes_required(()),
self.unknown2.heap_bytes_required(()),
self.parent_id.heap_bytes_required(()),
self.position.heap_bytes_required(()),
self.id.heap_bytes_required(()),
self.raw_is_category.heap_bytes_required(()),
self.offsets
.heap_bytes_required(self.subtype.get_offset_size()),
]
.iter()
.sum()
}
}

// https://djl-analysis.deepsymmetry.org/rekordbox-export-analysis/exports.html#tag-track-rows
/// M*N junction table between tags and tracks.
#[binrw]
Expand All @@ -108,6 +149,20 @@ pub struct TrackTag {
pub unknown_const: u32, // always 3?
}

impl PageHeapObject for TrackTag {
type Args<'a> = ();
fn heap_bytes_required(&self, _: ()) -> u16 {
[
(0u32).heap_bytes_required(()),
self.track_id.heap_bytes_required(()),
self.tag_id.heap_bytes_required(()),
self.unknown_const.heap_bytes_required(()),
]
.iter()
.sum()
}
}

/// The type of ext pages found inside a `Table`.
#[binrw]
#[brw(little)]
Expand All @@ -134,8 +189,18 @@ pub enum ExtPageType {
pub enum ExtRow {
/// Contains the album name, along with an ID of the corresponding artist.
#[br(pre_assert(page_type == ExtPageType::Tag))]
Tag(#[bw(pad_after = 0, align_after = 4)] TagOrCategory),
Tag(#[bw(align_after = 4)] TagOrCategory),
/// Contains the artist name and ID.
#[br(pre_assert(page_type == ExtPageType::TrackTag))]
TrackTag(#[bw(pad_after = 0, align_after = 4)] TrackTag),
TrackTag(#[bw(align_after = 4)] TrackTag),
}

impl PageHeapObject for ExtRow {
type Args<'a> = ();
fn heap_bytes_required(&self, _: ()) -> u16 {
match self {
ExtRow::Tag(tag_or_category) => tag_or_category.heap_bytes_required(()),
ExtRow::TrackTag(track_tag) => track_tag.heap_bytes_required(()),
}
}
}
Loading
Loading