ok...
This commit is contained in:
1 parent
66c5207ae1
commit
07017c5def
3 files changed
+153
-1
No files matched your search
@@ -0,0 +1,133 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use binrw::BinRead;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use binrw::Endian;
|
||||||
|
use rekordcrate::pdb::{
|
||||||
|
Header, PageType, Row,
|
||||||
|
Track, TrackId,
|
||||||
|
PlaylistTreeNode, PlaylistTreeNodeId,
|
||||||
|
PlaylistEntry,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DbError {
|
||||||
|
UnknownPlaylist,
|
||||||
|
UnknownTrack,
|
||||||
|
PositionOutOfBounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Database {
|
||||||
|
pub header: Header,
|
||||||
|
pub tracks: BTreeMap<TrackId, Track>,
|
||||||
|
pub nodes: BTreeMap<PlaylistTreeNodeId, PlaylistTreeNode>,
|
||||||
|
// Keep playlist entries ordered by entry_index
|
||||||
|
pub entries: BTreeMap<PlaylistTreeNodeId, Vec<PlaylistEntry>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
pub fn load_from_pdb(path: impl AsRef<Path>) -> rekordcrate::Result<Self> {
|
||||||
|
let mut f = std::fs::File::open(path)?;
|
||||||
|
let header = Header::read(&mut f)?; // uses the type’s endianness
|
||||||
|
|
||||||
|
let mut tracks: BTreeMap<TrackId, Track> = BTreeMap::new();
|
||||||
|
let mut nodes: BTreeMap<PlaylistTreeNodeId, PlaylistTreeNode> = BTreeMap::new();
|
||||||
|
let mut entries: BTreeMap<PlaylistTreeNodeId, Vec<PlaylistEntry>> = BTreeMap::new();
|
||||||
|
|
||||||
|
for table in &header.tables {
|
||||||
|
let pages = header.read_pages(&mut f, Endian::NATIVE, (&table.first_page, &table.last_page))?;
|
||||||
|
|
||||||
|
for page in pages {
|
||||||
|
for rg in page.row_groups {
|
||||||
|
for row in rg.present_rows() {
|
||||||
|
match row {
|
||||||
|
Row::Track(t) => {
|
||||||
|
tracks.insert(t.id, t.clone());
|
||||||
|
}
|
||||||
|
Row::PlaylistTreeNode(n) => {
|
||||||
|
nodes.insert(n.id, n.clone());
|
||||||
|
}
|
||||||
|
Row::PlaylistEntry(e) => {
|
||||||
|
entries.entry(e.playlist_id).or_default().push(e.clone());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure deterministic ordering within each playlist
|
||||||
|
for v in entries.values_mut() {
|
||||||
|
v.sort_by_key(|e| e.entry_index); // needs `pub entry_index`
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { header, tracks, nodes, entries })
|
||||||
|
}
|
||||||
|
/// Export to low-level tables.
|
||||||
|
pub fn into_tables(
|
||||||
|
self,
|
||||||
|
) -> (
|
||||||
|
Header,
|
||||||
|
Vec<Track>,
|
||||||
|
Vec<PlaylistTreeNode>,
|
||||||
|
Vec<(PlaylistTreeNodeId, PlaylistEntry)>,
|
||||||
|
) {
|
||||||
|
let nodes = self.nodes.into_values().collect();
|
||||||
|
let tracks = self.tracks.into_values().collect();
|
||||||
|
let mut flat_entries = Vec::new();
|
||||||
|
for (pid, vs) in self.entries {
|
||||||
|
for e in vs {
|
||||||
|
flat_entries.push((pid, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(self.header, tracks, nodes, flat_entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a ready-made entry (you build it).
|
||||||
|
pub fn add_entry(
|
||||||
|
&mut self,
|
||||||
|
playlist_id: PlaylistTreeNodeId,
|
||||||
|
entry: PlaylistEntry,
|
||||||
|
position: Option<usize>,
|
||||||
|
) -> Result<(), DbError> {
|
||||||
|
if !self.nodes.contains_key(&playlist_id) {
|
||||||
|
return Err(DbError::UnknownPlaylist);
|
||||||
|
}
|
||||||
|
let vec = self.entries.entry(playlist_id).or_default();
|
||||||
|
match position {
|
||||||
|
Some(i) if i <= vec.len() => vec.insert(i, entry),
|
||||||
|
Some(_) => return Err(DbError::PositionOutOfBounds),
|
||||||
|
None => vec.push(entry),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience: you pass a track id and a constructor for `PlaylistEntry`.
|
||||||
|
pub fn add_track_to_playlist_with<E>(
|
||||||
|
&mut self,
|
||||||
|
playlist_id: PlaylistTreeNodeId,
|
||||||
|
track_id: TrackId,
|
||||||
|
position: Option<usize>,
|
||||||
|
make_entry: E,
|
||||||
|
) -> Result<(), DbError>
|
||||||
|
where
|
||||||
|
E: FnOnce(TrackId) -> PlaylistEntry,
|
||||||
|
{
|
||||||
|
if !self.nodes.contains_key(&playlist_id) {
|
||||||
|
return Err(DbError::UnknownPlaylist);
|
||||||
|
}
|
||||||
|
if !self.tracks.contains_key(&track_id) {
|
||||||
|
return Err(DbError::UnknownTrack);
|
||||||
|
}
|
||||||
|
let entry = make_entry(track_id);
|
||||||
|
self.add_entry(playlist_id, entry, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn alloc_node_id(&mut self) -> u32 {
|
||||||
|
// let id = self.next_node_id;
|
||||||
|
// self.next_node_id = self.next_node_id.saturating_add(1);
|
||||||
|
// id
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
mod db;
|
||||||
|
|
||||||
|
use db::Database;
|
||||||
|
|
||||||
|
// Example
|
||||||
|
fn main() -> rekordcrate::Result<()> {
|
||||||
|
let db = Database::load_from_pdb("ntr.pdb")?;
|
||||||
|
println!("tracks: {}, playlists: {}", db.tracks.len(), db.nodes.len());
|
||||||
|
|
||||||
|
for n in db.nodes {
|
||||||
|
println!("{:?}", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
|
mod db;
|
||||||
|
|
||||||
use binrw::BinRead;
|
use binrw::BinRead;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use rekordcrate::pdb::{Header, Row, PageType, PlaylistTreeNode, PlaylistTreeNodeId};
|
use rekordcrate::pdb::{Header, Row, PageType, PlaylistTreeNode, PlaylistTreeNodeId};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use db::Database;
|
||||||
|
|
||||||
fn list_playlists(path: &PathBuf) -> rekordcrate::Result<()> {
|
fn list_playlists(path: &PathBuf) -> rekordcrate::Result<()> {
|
||||||
|
|
||||||
fn print_children_of(
|
fn print_children_of(
|
||||||
@@ -67,7 +71,7 @@ fn list_playlists(path: &PathBuf) -> rekordcrate::Result<()> {
|
|||||||
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let p = PathBuf::from("../data/PIONEER/rekordbox/export.pdb");
|
let p = PathBuf::from("./ntr.pdb");
|
||||||
|
|
||||||
list_playlists(&p)?;
|
list_playlists(&p)?;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user