rusting
This commit is contained in:
1 parent
0a970d6c60
commit
76bf94823b
7 files changed
+281
-32
No files matched your search
+1
-1
@@ -13,6 +13,6 @@ surf.sublime-workspace
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
**/target
|
||||
maple.sublime-workspace
|
||||
*.secret.yaml
|
||||
Generated
+120
-2
@@ -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"
|
||||
@@ -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.
@@ -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(())
|
||||
}
|
||||
@@ -2,7 +2,10 @@
|
||||
"folders":
|
||||
[
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
]
|
||||
"path": ".",
|
||||
},
|
||||
{
|
||||
"path": "/home/ntr/code/rekordcrate"
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user