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 std::path::PathBuf;
|
||||
use rekordcrate::pdb::{Header, Row, PageType, PlaylistTreeNode, PlaylistTreeNodeId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use db::Database;
|
||||
|
||||
fn list_playlists(path: &PathBuf) -> rekordcrate::Result<()> {
|
||||
|
||||
fn print_children_of(
|
||||
@@ -67,7 +71,7 @@ fn list_playlists(path: &PathBuf) -> rekordcrate::Result<()> {
|
||||
|
||||
|
||||
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)?;
|
||||
|
||||
|
||||
Reference in new issue
Block a user