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
|
# Added by cargo
|
||||||
|
|
||||||
/target
|
**/target
|
||||||
maple.sublime-workspace
|
maple.sublime-workspace
|
||||||
*.secret.yaml
|
*.secret.yaml
|
||||||
Generated
+120
-2
@@ -94,6 +94,15 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
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]]
|
[[package]]
|
||||||
name = "binrw"
|
name = "binrw"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
@@ -118,6 +127,12 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.0"
|
version = "3.19.0"
|
||||||
@@ -244,6 +259,18 @@ version = "1.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
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]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@@ -254,12 +281,36 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
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]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -312,6 +363,17 @@ version = "0.2.175"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
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]]
|
[[package]]
|
||||||
name = "lofty"
|
name = "lofty"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
@@ -348,8 +410,12 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
|||||||
name = "maple"
|
name = "maple"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base85",
|
||||||
|
"binrw",
|
||||||
|
"flate2",
|
||||||
"lofty",
|
"lofty",
|
||||||
"rekordcrate",
|
"rekordcrate",
|
||||||
|
"rusqlite",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -456,6 +522,12 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.101"
|
version = "1.0.101"
|
||||||
@@ -528,7 +600,21 @@ dependencies = [
|
|||||||
"parse-display",
|
"parse-display",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
@@ -572,6 +658,12 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -629,13 +721,33 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.15"
|
version = "2.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850"
|
checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
@@ -661,6 +773,12 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -7,6 +7,13 @@ edition = "2024"
|
|||||||
rekordcrate = "0.3.0"
|
rekordcrate = "0.3.0"
|
||||||
lofty = "0.21"
|
lofty = "0.21"
|
||||||
walkdir = "2.4"
|
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]]
|
[[bin]]
|
||||||
name = "playlists"
|
name = "playlists"
|
||||||
@@ -14,4 +21,4 @@ path = "src/playlists.rs"
|
|||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "library"
|
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":
|
"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