This commit is contained in:
Nathan Rashleigh committed 2025-09-07 17:29:37 +10:00
1 parent 0a970d6c60
commit 76bf94823b
7 files changed
+281 -32

No files matched your search

+1 -1
View File
@@ -13,6 +13,6 @@ surf.sublime-workspace
# Added by cargo
/target
**/target
maple.sublime-workspace
*.secret.yaml
+120 -2
View File
@@ -94,6 +94,15 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base85"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36915bbaca237c626689b5bd14d02f2ba7a5a359d30a2a08be697392e3718079"
dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "binrw"
version = "0.14.1"
@@ -118,6 +127,12 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "bitflags"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]]
name = "bumpalo"
version = "3.19.0"
@@ -244,6 +259,18 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "flate2"
version = "1.1.2"
@@ -254,12 +281,36 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown",
]
[[package]]
name = "heck"
version = "0.5.0"
@@ -312,6 +363,17 @@ version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libsqlite3-sys"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "lofty"
version = "0.21.1"
@@ -348,8 +410,12 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
name = "maple"
version = "0.1.0"
dependencies = [
"base85",
"binrw",
"flate2",
"lofty",
"rekordcrate",
"rusqlite",
"walkdir",
]
@@ -456,6 +522,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro2"
version = "1.0.101"
@@ -528,7 +600,21 @@ dependencies = [
"parse-display",
"quick-xml",
"serde",
"thiserror",
"thiserror 2.0.15",
]
[[package]]
name = "rusqlite"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
@@ -572,6 +658,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "static_assertions"
version = "1.1.0"
@@ -629,13 +721,33 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850"
dependencies = [
"thiserror-impl",
"thiserror-impl 2.0.15",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
@@ -661,6 +773,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "walkdir"
version = "2.5.0"
+8 -1
View File
@@ -7,6 +7,13 @@ edition = "2024"
rekordcrate = "0.3.0"
lofty = "0.21"
walkdir = "2.4"
binrw = "0.14.1"
base85 = "2.0.0"
flate2 = "1.1.2"
[dependencies.rusqlite]
version = "0.37.0"
features = ["bundled-sqlcipher"]
[[bin]]
name = "playlists"
@@ -14,4 +21,4 @@ path = "src/playlists.rs"
[[bin]]
name = "library"
path = "src/main.rs"
path = "src/library.rs"
File renamed without changes.
+146
View File
@@ -0,0 +1,146 @@
use rusqlite::{params, Connection, Result};
use std::io::Read;
use flate2::read::ZlibDecoder;
use base85;
const BLOB_KEY: &[u8] = b"657f48f84c437cc1";
const BLOB: &[u8] = b"PN_Pq^*N>(JYe*u^8;Yg76HuZ<mR13S?=>)b9;DpoTXV(6ItkU`}8*m6tx_I{Solh_N#dfe{v=";
fn deobfuscate() -> Result<String, Box<dyn std::error::Error>> {
// 1. Base85 decode
let data = base85::decode(std::str::from_utf8(BLOB)?)?;
// 2. XOR with key
let xored: Vec<u8> = data
.iter()
.enumerate()
.map(|(i, &b)| b ^ BLOB_KEY[i % BLOB_KEY.len()])
.collect();
// 3. zlib decompress
let mut decoder = ZlibDecoder::new(&xored[..]);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed)?;
// 4. UTF-8 decode
Ok(String::from_utf8(decompressed)?)
}
// Structs for mapping rows
#[derive(Debug)]
struct Playlist {
id: i64,
name: String,
}
#[derive(Debug)]
struct Track {
id: i64,
title: String,
}
// List all playlists
fn list_playlists(conn: &Connection) -> Result<Vec<Playlist>> {
let mut stmt = conn.prepare("SELECT ID, Name FROM plPlaylist ORDER BY ID")?;
let playlists = stmt.query_map([], |row| {
Ok(Playlist {
id: row.get(0)?,
name: row.get(1)?,
})
})?;
Ok(playlists.filter_map(|x| x.ok()).collect())
}
// List tracks in a playlist
fn list_tracks_in_playlist(conn: &Connection, playlist_id: i64) -> Result<Vec<Track>> {
let mut stmt = conn.prepare(
"SELECT c.ID, c.Name
FROM plSongPlaylistRel r
JOIN plContent c ON r.SongID = c.ID
WHERE r.PlaylistID = ?
ORDER BY r.SortOrder",
)?;
let tracks = stmt.query_map([playlist_id], |row| {
Ok(Track {
id: row.get(0)?,
title: row.get(1)?,
})
})?;
Ok(tracks.filter_map(|x| x.ok()).collect())
}
// Add a track to a playlist
fn add_track_to_playlist(conn: &Connection, playlist_id: i64, track_id: i64) -> Result<()> {
// Get next ID for relation
let next_id: i64 = conn.query_row(
"SELECT IFNULL(MAX(ID), 0) + 1 FROM plSongPlaylistRel",
[],
|row| row.get(0),
)?;
// Get next sort order for playlist
let next_sort: i64 = conn.query_row(
"SELECT IFNULL(MAX(SortOrder), 0) + 1 FROM plSongPlaylistRel WHERE PlaylistID = ?",
[playlist_id],
|row| row.get(0),
)?;
conn.execute(
"INSERT INTO plSongPlaylistRel (ID, PlaylistID, SongID, SortOrder)
VALUES (?, ?, ?, ?)",
params![next_id, playlist_id, track_id, next_sort],
)?;
Ok(())
}
// Remove a track from a playlist
fn remove_track_from_playlist(conn: &Connection, playlist_id: i64, track_id: i64) -> Result<()> {
conn.execute(
"DELETE FROM plSongPlaylistRel WHERE PlaylistID = ? AND SongID = ?",
params![playlist_id, track_id],
)?;
Ok(())
}
/// List all tables in a Rekordbox SQLite DB (master.db or export.pdb)
fn list_tables(conn: &Connection) -> Result<Vec<String>> {
let mut stmt = conn.prepare(
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"
)?;
let rows = stmt.query_map([], |row| row.get(0))?;
let mut tables = Vec::new();
for r in rows {
if let Ok(name) = r {
tables.push(name);
}
}
Ok(tables)
}
// Open Rekordbox DB (unencrypted). For Rekordbox 6+, needs SQLCipher build.
fn open_rekordbox_db(path: &str, key: String) -> Result<Connection> {
let conn = Connection::open(path)?;
// conn.pragma_update(None, "key", key)?;
Ok(conn)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let key = deobfuscate()?;
let conn = open_rekordbox_db("../data/PIONEER/rekordbox/export.pdb", key)?;
list_tables(&conn)?;
// // Example: list playlists
// for p in list_playlists(&conn)? {
// println!("Playlist {}: {}", p.id, p.name);
// }
// // Example: add track 123 to playlist 10
// add_track_to_playlist(&conn, 10, 123)?;
Ok(())
}
+6 -3
View File
@@ -2,7 +2,10 @@
"folders":
[
{
"path": "."
}
]
"path": ".",
},
{
"path": "/home/ntr/code/rekordcrate"
},
],
}
-25
View File
@@ -1,25 +0,0 @@
use std::path::Path;
fn main() {
let pdb_path = "data/PIONEER/rekordbox/export.pdb";
if !Path::new(pdb_path).exists() {
println!("Pioneer database {} not found", pdb_path);
return;
}
match rekordcrate::parse_pdb(pdb_path) {
Ok(database) => {
println!("Playlists in {}:\n", pdb_path);
for playlist in &database.playlists {
println!(" {}", playlist.name);
}
println!("\nTotal playlists: {}", database.playlists.len());
}
Err(e) => {
println!("Error reading Pioneer database: {}", e);
}
}
}