mirror of
https://github.com/Dangoware/dmp-core.git
synced 2025-04-19 13:22:54 -05:00
Added db_reader, implemented ExternalLibrary, updated Playlist
This commit is contained in:
parent
15b4984054
commit
594e426a3f
10 changed files with 791 additions and 5 deletions
|
@ -29,3 +29,5 @@ gstreamer = "0.21.2"
|
|||
glib = "0.18.3"
|
||||
crossbeam-channel = "0.5.8"
|
||||
crossbeam = "0.8.2"
|
||||
quick-xml = "0.31.0"
|
||||
leb128 = "0.2.5"
|
14
src/lib.rs
14
src/lib.rs
|
@ -3,6 +3,20 @@ pub mod music_storage {
|
|||
pub mod playlist;
|
||||
mod utils;
|
||||
pub mod music_collection;
|
||||
pub mod db_reader {
|
||||
pub mod foobar {
|
||||
pub mod reader;
|
||||
}
|
||||
pub mod musicbee {
|
||||
pub mod utils;
|
||||
pub mod reader;
|
||||
}
|
||||
pub mod xml {
|
||||
pub mod reader;
|
||||
}
|
||||
pub mod common;
|
||||
pub mod extern_library;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod music_controller {
|
||||
|
|
51
src/music_storage/db_reader/common.rs
Normal file
51
src/music_storage/db_reader/common.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
|
||||
pub fn get_bytes<const S: usize>(iterator: &mut std::vec::IntoIter<u8>) -> [u8; S] {
|
||||
let mut bytes = [0; S];
|
||||
|
||||
for i in 0..S {
|
||||
bytes[i] = iterator.next().unwrap();
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
pub fn get_bytes_vec(iterator: &mut std::vec::IntoIter<u8>, number: usize) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
for _ in 0..number {
|
||||
bytes.push(iterator.next().unwrap());
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// Converts the windows DateTime into Chrono DateTime
|
||||
pub fn get_datetime(iterator: &mut std::vec::IntoIter<u8>, topbyte: bool) -> DateTime<Utc> {
|
||||
let mut datetime_i64 = i64::from_le_bytes(get_bytes(iterator));
|
||||
|
||||
if topbyte {
|
||||
// Zero the topmost byte
|
||||
datetime_i64 = datetime_i64 & 0x00FFFFFFFFFFFFFFF;
|
||||
}
|
||||
|
||||
if datetime_i64 <= 0 {
|
||||
return Utc.timestamp_opt(0, 0).unwrap();
|
||||
}
|
||||
|
||||
let unix_time_ticks = datetime_i64 - 621355968000000000;
|
||||
|
||||
let unix_time_seconds = unix_time_ticks / 10000000;
|
||||
|
||||
let unix_time_nanos = match (unix_time_ticks as f64 / 10000000.0) - unix_time_seconds as f64
|
||||
> 0.0
|
||||
{
|
||||
true => ((unix_time_ticks as f64 / 10000000.0) - unix_time_seconds as f64) * 1000000000.0,
|
||||
false => 0.0,
|
||||
};
|
||||
|
||||
Utc.timestamp_opt(unix_time_seconds, unix_time_nanos as u32)
|
||||
.unwrap()
|
||||
}
|
9
src/music_storage/db_reader/extern_library.rs
Normal file
9
src/music_storage/db_reader/extern_library.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
|
||||
pub trait ExternalLibrary {
|
||||
fn from_file(&mut self, file: &PathBuf) -> Self;
|
||||
fn write(&self) {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
176
src/music_storage/db_reader/foobar/reader.rs
Normal file
176
src/music_storage/db_reader/foobar/reader.rs
Normal file
|
@ -0,0 +1,176 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use std::{fs::File, io::Read, path::PathBuf, time::Duration};
|
||||
|
||||
use crate::music_storage::db_reader::common::{get_bytes, get_bytes_vec, get_datetime};
|
||||
|
||||
const MAGIC: [u8; 16] = [
|
||||
0xE1, 0xA0, 0x9C, 0x91, 0xF8, 0x3C, 0x77, 0x42, 0x85, 0x2C, 0x3B, 0xCC, 0x14, 0x01, 0xD3, 0xF2,
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FoobarPlaylist {
|
||||
path: PathBuf,
|
||||
metadata: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FoobarPlaylistTrack {
|
||||
flags: i32,
|
||||
file_name: String,
|
||||
subsong_index: i32,
|
||||
file_size: i64,
|
||||
file_time: DateTime<Utc>,
|
||||
duration: Duration,
|
||||
rpg_album: u32,
|
||||
rpg_track: u32,
|
||||
rpk_album: u32,
|
||||
rpk_track: u32,
|
||||
entries: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl FoobarPlaylist {
|
||||
pub fn new(path: &String) -> Self {
|
||||
FoobarPlaylist {
|
||||
path: PathBuf::from(path),
|
||||
metadata: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_meta_offset(&self, offset: usize) -> String {
|
||||
let mut result_vec = Vec::new();
|
||||
|
||||
let mut i = offset;
|
||||
loop {
|
||||
if self.metadata[i] == 0x00 {
|
||||
break;
|
||||
}
|
||||
|
||||
result_vec.push(self.metadata[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
String::from_utf8_lossy(&result_vec).into()
|
||||
}
|
||||
|
||||
/// Reads the entire MusicBee library and returns relevant values
|
||||
/// as a `Vec` of `Song`s
|
||||
pub fn read(&mut self) -> Result<Vec<FoobarPlaylistTrack>, Box<dyn std::error::Error>> {
|
||||
let mut f = File::open(&self.path).unwrap();
|
||||
let mut buffer = Vec::new();
|
||||
let mut retrieved_songs: Vec<FoobarPlaylistTrack> = Vec::new();
|
||||
|
||||
// Read the whole file
|
||||
f.read_to_end(&mut buffer)?;
|
||||
|
||||
let mut buf_iter = buffer.into_iter();
|
||||
|
||||
// Parse the header
|
||||
let magic = get_bytes::<16>(&mut buf_iter);
|
||||
if magic != MAGIC {
|
||||
return Err("Magic bytes mismatch!".into());
|
||||
}
|
||||
|
||||
let meta_size = i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize;
|
||||
self.metadata = get_bytes_vec(&mut buf_iter, meta_size);
|
||||
let track_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
// Read all the track fields
|
||||
for _ in 0..track_count {
|
||||
let flags = i32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
let has_metadata = (0x01 & flags) != 0;
|
||||
let has_padding = (0x04 & flags) != 0;
|
||||
|
||||
let file_name_offset = i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize;
|
||||
let file_name = self.get_meta_offset(file_name_offset);
|
||||
|
||||
let subsong_index = i32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
if !has_metadata {
|
||||
let track = FoobarPlaylistTrack {
|
||||
file_name,
|
||||
subsong_index,
|
||||
..Default::default()
|
||||
};
|
||||
retrieved_songs.push(track);
|
||||
continue;
|
||||
}
|
||||
|
||||
let file_size = i64::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
let file_time = get_datetime(&mut buf_iter, false);
|
||||
|
||||
let duration = Duration::from_nanos(u64::from_le_bytes(get_bytes(&mut buf_iter)) / 100);
|
||||
|
||||
let rpg_album = u32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
let rpg_track = u32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
let rpk_album = u32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
let rpk_track = u32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
get_bytes::<4>(&mut buf_iter);
|
||||
|
||||
let mut entries = Vec::new();
|
||||
let primary_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
let secondary_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
let _secondary_offset = i32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
// Get primary keys
|
||||
for _ in 0..primary_count {
|
||||
println!("{}", i32::from_le_bytes(get_bytes(&mut buf_iter)));
|
||||
|
||||
let key =
|
||||
self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
|
||||
|
||||
entries.push((key, String::new()));
|
||||
}
|
||||
|
||||
// Consume unknown 32 bit value
|
||||
println!("unk");
|
||||
get_bytes::<4>(&mut buf_iter);
|
||||
|
||||
// Get primary values
|
||||
for i in 0..primary_count {
|
||||
println!("primkey {i}");
|
||||
|
||||
let value =
|
||||
self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
|
||||
|
||||
entries[i as usize].1 = value;
|
||||
}
|
||||
|
||||
// Get secondary Keys
|
||||
for _ in 0..secondary_count {
|
||||
let key =
|
||||
self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
|
||||
let value =
|
||||
self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
|
||||
entries.push((key, value));
|
||||
}
|
||||
|
||||
if has_padding {
|
||||
get_bytes::<64>(&mut buf_iter);
|
||||
}
|
||||
|
||||
let track = FoobarPlaylistTrack {
|
||||
flags,
|
||||
file_name,
|
||||
subsong_index,
|
||||
file_size,
|
||||
file_time,
|
||||
duration,
|
||||
rpg_album,
|
||||
rpg_track,
|
||||
rpk_album,
|
||||
rpk_track,
|
||||
entries,
|
||||
};
|
||||
|
||||
retrieved_songs.push(track);
|
||||
}
|
||||
|
||||
Ok(retrieved_songs)
|
||||
}
|
||||
}
|
220
src/music_storage/db_reader/musicbee/reader.rs
Normal file
220
src/music_storage/db_reader/musicbee/reader.rs
Normal file
|
@ -0,0 +1,220 @@
|
|||
use super::utils::get_string;
|
||||
use crate::music_storage::db_reader::common::{get_bytes, get_datetime};
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct MusicBeeDatabase {
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl MusicBeeDatabase {
|
||||
pub fn new(path: String) -> MusicBeeDatabase {
|
||||
MusicBeeDatabase { path }
|
||||
}
|
||||
|
||||
/// Reads the entire MusicBee library and returns relevant values
|
||||
/// as a `Vec` of `Song`s
|
||||
pub fn read(&self) -> Result<Vec<MusicBeeSong>, Box<dyn std::error::Error>> {
|
||||
let mut f = File::open(&self.path).unwrap();
|
||||
let mut buffer = Vec::new();
|
||||
let mut retrieved_songs: Vec<MusicBeeSong> = Vec::new();
|
||||
|
||||
// Read the whole file
|
||||
f.read_to_end(&mut buffer)?;
|
||||
|
||||
let mut buf_iter = buffer.into_iter();
|
||||
|
||||
// Get the song count from the first 4 bytes
|
||||
// and then right shift it by 8 for some reason
|
||||
let mut database_song_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
database_song_count = database_song_count >> 8;
|
||||
|
||||
let mut song_count = 0;
|
||||
loop {
|
||||
// If the file designation is 1, then the end of the database
|
||||
// has been reached
|
||||
let file_designation = match buf_iter.next() {
|
||||
Some(1) => break,
|
||||
Some(value) => value,
|
||||
None => break,
|
||||
};
|
||||
|
||||
song_count += 1;
|
||||
|
||||
// Get the file status. Unknown what this means
|
||||
let status = buf_iter.next().unwrap();
|
||||
|
||||
buf_iter.next(); // Read in a byte to throw it away
|
||||
|
||||
// Get the play count
|
||||
let play_count = u16::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
// Get the time the song was last played, stored as a signed 64 bit number of microseconds
|
||||
let last_played = get_datetime(buf_iter.by_ref(), true);
|
||||
|
||||
// Get the number of times the song was skipped
|
||||
let skip_count = u16::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
// Get the path to the song
|
||||
let path = get_string(buf_iter.by_ref());
|
||||
|
||||
// Get the file size
|
||||
let file_size = i32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
// Get the sample rate
|
||||
let sample_rate = i32::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
// Get the channel count
|
||||
let channel_count = buf_iter.next().unwrap();
|
||||
|
||||
// Get the bitrate type (CBR, VBR, etc.)
|
||||
let bitrate_type = buf_iter.next().unwrap();
|
||||
|
||||
// Get the actual bitrate
|
||||
let bitrate = i16::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
|
||||
// Get the track length in milliseconds
|
||||
let track_length =
|
||||
Duration::from_millis(i32::from_le_bytes(get_bytes(&mut buf_iter)) as u64);
|
||||
|
||||
// Get the date added and modified in the same format
|
||||
let date_added = get_datetime(buf_iter.by_ref(), true);
|
||||
let date_modified = get_datetime(buf_iter.by_ref(), true);
|
||||
|
||||
// Gets artwork information
|
||||
//
|
||||
// Artworks are stored as chunks describing the type
|
||||
// (embedded, file), and some other information.
|
||||
let mut artwork: Vec<MusicBeeAlbumArt> = vec![];
|
||||
loop {
|
||||
let artwork_type = buf_iter.next().unwrap();
|
||||
if artwork_type > 253 {
|
||||
break;
|
||||
}
|
||||
|
||||
let unknown_string = get_string(buf_iter.by_ref());
|
||||
let storage_mode = buf_iter.next().unwrap();
|
||||
let storage_path = get_string(buf_iter.by_ref());
|
||||
|
||||
artwork.push(MusicBeeAlbumArt {
|
||||
artwork_type,
|
||||
unknown_string,
|
||||
storage_mode,
|
||||
storage_path,
|
||||
});
|
||||
}
|
||||
|
||||
buf_iter.next(); // Read in a byte to throw it away
|
||||
|
||||
// Gets all the tags on the song in the database
|
||||
let mut tags: Vec<MusicBeeTag> = vec![];
|
||||
loop {
|
||||
// If the tag code is 0, the end of the block has been reached, so break.
|
||||
//
|
||||
// If the tag code is 255, it pertains to some CUE file values that are not known
|
||||
// throw away these values
|
||||
let tag_code = match buf_iter.next() {
|
||||
Some(0) => break,
|
||||
Some(255) => {
|
||||
let repeats = u16::from_le_bytes(get_bytes(&mut buf_iter));
|
||||
for _ in 0..(repeats * 13) - 2 {
|
||||
buf_iter.next().unwrap();
|
||||
}
|
||||
|
||||
255
|
||||
}
|
||||
Some(value) => value,
|
||||
None => panic!(),
|
||||
};
|
||||
|
||||
// Get the string value of the tag
|
||||
let tag_value = get_string(buf_iter.by_ref());
|
||||
tags.push(MusicBeeTag {
|
||||
tag_code,
|
||||
tag_value,
|
||||
});
|
||||
}
|
||||
|
||||
// Construct the finished song and add it to the vec
|
||||
let constructed_song = MusicBeeSong {
|
||||
file_designation,
|
||||
status,
|
||||
play_count,
|
||||
last_played,
|
||||
skip_count,
|
||||
path,
|
||||
file_size,
|
||||
sample_rate,
|
||||
channel_count,
|
||||
bitrate_type,
|
||||
bitrate,
|
||||
track_length,
|
||||
date_added,
|
||||
date_modified,
|
||||
artwork,
|
||||
tags,
|
||||
};
|
||||
|
||||
retrieved_songs.push(constructed_song);
|
||||
}
|
||||
|
||||
println!("The database claims you have: {database_song_count} songs\nThe retrieved number is: {song_count} songs");
|
||||
|
||||
match database_song_count == song_count {
|
||||
true => Ok(retrieved_songs),
|
||||
false => Err("Song counts do not match!".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MusicBeeTag {
|
||||
tag_code: u8,
|
||||
tag_value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MusicBeeAlbumArt {
|
||||
artwork_type: u8,
|
||||
unknown_string: String,
|
||||
storage_mode: u8,
|
||||
storage_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MusicBeeSong {
|
||||
file_designation: u8,
|
||||
status: u8,
|
||||
play_count: u16,
|
||||
pub last_played: DateTime<Utc>,
|
||||
skip_count: u16,
|
||||
path: String,
|
||||
file_size: i32,
|
||||
sample_rate: i32,
|
||||
channel_count: u8,
|
||||
bitrate_type: u8,
|
||||
bitrate: i16,
|
||||
track_length: Duration,
|
||||
date_added: DateTime<Utc>,
|
||||
date_modified: DateTime<Utc>,
|
||||
|
||||
/* Album art stuff */
|
||||
artwork: Vec<MusicBeeAlbumArt>,
|
||||
|
||||
/* All tags */
|
||||
tags: Vec<MusicBeeTag>,
|
||||
}
|
||||
|
||||
impl MusicBeeSong {
|
||||
pub fn get_tag_code(self, code: u8) -> Option<String> {
|
||||
for tag in &self.tags {
|
||||
if tag.tag_code == code {
|
||||
return Some(tag.tag_value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
29
src/music_storage/db_reader/musicbee/utils.rs
Normal file
29
src/music_storage/db_reader/musicbee/utils.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use leb128;
|
||||
|
||||
/// Gets a string from the MusicBee database format
|
||||
///
|
||||
/// The length of the string is defined by an LEB128 encoded value at the beginning, followed by the string of that length
|
||||
pub fn get_string(iterator: &mut std::vec::IntoIter<u8>) -> String {
|
||||
let mut string_length = iterator.next().unwrap() as usize;
|
||||
if string_length == 0 {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// Decode the LEB128 value
|
||||
let mut leb_bytes: Vec<u8> = vec![];
|
||||
loop {
|
||||
leb_bytes.push(string_length as u8);
|
||||
|
||||
if string_length >> 7 != 1 {
|
||||
break;
|
||||
}
|
||||
string_length = iterator.next().unwrap() as usize;
|
||||
}
|
||||
string_length = leb128::read::unsigned(&mut leb_bytes.as_slice()).unwrap() as usize;
|
||||
|
||||
let mut string_bytes = vec![];
|
||||
for _ in 0..string_length {
|
||||
string_bytes.push(iterator.next().unwrap());
|
||||
}
|
||||
String::from_utf8(string_bytes).unwrap()
|
||||
}
|
222
src/music_storage/db_reader/xml/reader.rs
Normal file
222
src/music_storage/db_reader/xml/reader.rs
Normal file
|
@ -0,0 +1,222 @@
|
|||
use quick_xml::events::Event;
|
||||
use quick_xml::reader::Reader;
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::io::Error;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::vec::Vec;
|
||||
|
||||
use chrono::prelude::*;
|
||||
|
||||
use crate::music_storage::db_reader::extern_library::ExternalLibrary;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct XmlLibrary {
|
||||
tracks: Vec<XMLSong>
|
||||
}
|
||||
impl XmlLibrary {
|
||||
fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
impl ExternalLibrary for XmlLibrary {
|
||||
fn from_file(&mut self, file: &PathBuf) -> Self {
|
||||
let mut reader = Reader::from_file(file).unwrap();
|
||||
reader.trim_text(true);
|
||||
//count every event, for fun ig?
|
||||
let mut count = 0;
|
||||
//count for skipping useless beginning key
|
||||
let mut count2 = 0;
|
||||
//number of grabbed songs
|
||||
let mut count3 = 0;
|
||||
//number of IDs skipped
|
||||
let mut count4 = 0;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut skip = false;
|
||||
|
||||
let mut converted_songs: Vec<XMLSong> = Vec::new();
|
||||
|
||||
|
||||
let mut song_tags: HashMap<String, String> = HashMap::new();
|
||||
let mut key: String = String::new();
|
||||
let mut tagvalue: String = String::new();
|
||||
let mut key_selected = false;
|
||||
|
||||
use std::time::Instant;
|
||||
let now = Instant::now();
|
||||
|
||||
loop {
|
||||
//push tag to song_tags map
|
||||
if !key.is_empty() && !tagvalue.is_empty() {
|
||||
song_tags.insert(key.clone(), tagvalue.clone());
|
||||
key.clear();
|
||||
tagvalue.clear();
|
||||
key_selected = false;
|
||||
|
||||
//end the song to start a new one, and turn turn current song map into XMLSong
|
||||
if song_tags.contains_key(&"Location".to_string()) {
|
||||
count3 += 1;
|
||||
//check for skipped IDs
|
||||
if &count3.to_string()
|
||||
!= song_tags.get_key_value(&"Track ID".to_string()).unwrap().1
|
||||
{
|
||||
count3 += 1;
|
||||
count4 += 1;
|
||||
}
|
||||
converted_songs.push(XMLSong::from_hashmap(&mut song_tags).unwrap());
|
||||
song_tags.clear();
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
match reader.read_event_into(&mut buf) {
|
||||
Ok(Event::Start(_)) => {
|
||||
count += 1;
|
||||
count2 += 1;
|
||||
}
|
||||
Ok(Event::Text(e)) => {
|
||||
if count < 17 && count != 10 {
|
||||
continue;
|
||||
}else if skip {
|
||||
skip = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
let text = e.unescape().unwrap().to_string();
|
||||
|
||||
if text == count2.to_string() && !key_selected {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Add the key/value depenidng on if the key is selected or not ⛩️sorry buzz
|
||||
|
||||
match key_selected {
|
||||
true => tagvalue.push_str(&text),
|
||||
false => {
|
||||
key.push_str(&text);
|
||||
if !key.is_empty() {
|
||||
key_selected = true
|
||||
} else {
|
||||
panic!("Key not selected?!")
|
||||
}
|
||||
}
|
||||
_ => panic!("WHAT DID YOU JUST DO?!🐰🐰🐰🐰"),
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
|
||||
Ok(Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
let elasped = now.elapsed();
|
||||
println!("\n\nXMLReader\n=========================================\n\nDone!\n{} songs grabbed in {:#?}\nIDs Skipped: {}", count3, elasped, count4);
|
||||
// dbg!(folder);
|
||||
self.tracks.append(converted_songs.as_mut());
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct XMLSong {
|
||||
pub id: i32,
|
||||
pub plays: i32,
|
||||
pub favorited: bool,
|
||||
pub banned: bool,
|
||||
pub rating: Option<u8>,
|
||||
pub format: Option<String>,
|
||||
pub song_type: Option<String>,
|
||||
pub last_played: Option<DateTime<Utc>>,
|
||||
pub date_added: Option<DateTime<Utc>>,
|
||||
pub date_modified: Option<DateTime<Utc>>,
|
||||
pub tags: BTreeMap<String, String>,
|
||||
pub location: String,
|
||||
}
|
||||
|
||||
impl XMLSong {
|
||||
pub fn new() -> XMLSong {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
|
||||
fn from_hashmap(map: &mut HashMap<String, String>) -> Result<XMLSong, Error> {
|
||||
let mut song = XMLSong::new();
|
||||
//get the path with the first bit chopped off
|
||||
let path_: String = map.get_key_value("Location").unwrap().1.clone();
|
||||
let track_type: String = map.get_key_value("Track Type").unwrap().1.clone();
|
||||
let path: String = match track_type.as_str() {
|
||||
"File" => {
|
||||
if path_.contains("file://localhost/") {
|
||||
path_.strip_prefix("file://localhost/").unwrap();
|
||||
}
|
||||
path_
|
||||
}
|
||||
"URL" => path_,
|
||||
_ => path_,
|
||||
};
|
||||
|
||||
for (key, value) in map {
|
||||
match key.as_str() {
|
||||
"Track ID" => song.id = value.parse().unwrap(),
|
||||
"Location" => song.location = path.to_string(),
|
||||
"Play Count" => song.plays = value.parse().unwrap(),
|
||||
"Love" => {
|
||||
//check if the track is (L)Loved or (B)Banned
|
||||
match value.as_str() {
|
||||
"L" => song.favorited = true,
|
||||
"B" => song.banned = false,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
"Rating" => song.rating = Some(value.parse().unwrap()),
|
||||
"Kind" => song.format = Some(value.to_string()),
|
||||
"Play Date UTC" => {
|
||||
song.last_played = Some(DateTime::<Utc>::from_str(value).unwrap())
|
||||
}
|
||||
"Date Added" => song.date_added = Some(DateTime::<Utc>::from_str(value).unwrap()),
|
||||
"Date Modified" => {
|
||||
song.date_modified = Some(DateTime::<Utc>::from_str(value).unwrap())
|
||||
}
|
||||
"Track Type" => song.song_type = Some(value.to_string()),
|
||||
_ => {
|
||||
song.tags.insert(key.to_string(), value.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
// println!("{:.2?}", song);
|
||||
Ok(song)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn get_folder(file: &PathBuf) -> String {
|
||||
let mut reader = Reader::from_file(file).unwrap();
|
||||
reader.trim_text(true);
|
||||
//count every event, for fun ig?
|
||||
let mut count = 0;
|
||||
let mut buf = Vec::new();
|
||||
let mut folder = String::new();
|
||||
loop {
|
||||
match reader.read_event_into(&mut buf) {
|
||||
Ok(Event::Start(_)) => {
|
||||
count += 1;
|
||||
}
|
||||
Ok(Event::Text(e)) => {
|
||||
if count == 10 {
|
||||
folder = String::from(
|
||||
e.unescape()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.strip_prefix("file://localhost/")
|
||||
.unwrap(),
|
||||
);
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
Err(_e) => {
|
||||
panic!("oh no! something happened in the public function `get_reader_from_xml()!`")
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize};
|
|||
use rayon::prelude::*;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub enum AlbumArt {
|
||||
Embedded(usize),
|
||||
External(URI),
|
||||
|
@ -115,7 +115,7 @@ impl ToString for Field {
|
|||
}
|
||||
|
||||
/// Stores information about a single song
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Song {
|
||||
pub location: URI,
|
||||
pub plays: i32,
|
||||
|
@ -276,7 +276,6 @@ impl Album<'_> {
|
|||
}
|
||||
|
||||
|
||||
|
||||
pub fn discs(&self) -> &BTreeMap<usize, Vec<&Song>> {
|
||||
&self.discs
|
||||
}
|
||||
|
|
|
@ -1,15 +1,68 @@
|
|||
use chrono::Duration;
|
||||
use walkdir::Error;
|
||||
|
||||
use crate::music_controller::config::Config;
|
||||
use std::{path::Path, default, thread::AccessError};
|
||||
|
||||
use super::{library::{AlbumArt, Song}, music_collection::MusicCollection};
|
||||
use super::{library::{AlbumArt, Song, self, Tag}, music_collection::MusicCollection};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Playlist<'a> {
|
||||
title: String,
|
||||
cover: Option<&'a AlbumArt>,
|
||||
tracks: Vec<&'a Song>,
|
||||
play_count: i32,
|
||||
play_time: Duration,
|
||||
}
|
||||
impl<'a> Playlist<'a> {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
pub fn play_count(&self) -> i32 {
|
||||
self.play_count
|
||||
}
|
||||
pub fn play_time(&self) -> chrono::Duration {
|
||||
self.play_time
|
||||
}
|
||||
pub fn set_tracks(&mut self, songs: Vec<&'a Song>) -> Result<(), Error> {
|
||||
self.tracks = songs;
|
||||
Ok(())
|
||||
}
|
||||
pub fn add_track(&mut self, song: &'a Song) -> Result<(), Error> {
|
||||
self.tracks.push(song);
|
||||
Ok(())
|
||||
}
|
||||
pub fn remove_track(&mut self, index: i32) -> Result<(), Error> {
|
||||
let bun: usize = index as usize;
|
||||
let mut name = String::new();
|
||||
if self.tracks.len() >= bun {
|
||||
name = String::from(self.tracks[bun].tags.get_key_value(&Tag::Title).unwrap().1);
|
||||
self.tracks.remove(bun);
|
||||
}
|
||||
dbg!(name);
|
||||
Ok(())
|
||||
}
|
||||
pub fn get_index(&self, song_name: &str) -> Option<usize> {
|
||||
let mut index = 0;
|
||||
if self.contains(&Tag::Title, song_name) {
|
||||
for track in &self.tracks {
|
||||
index += 1;
|
||||
if song_name == track.tags.get_key_value(&Tag::Title).unwrap().1 {
|
||||
dbg!("Index gotted! ",index);
|
||||
return Some(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn contains(&self, tag: &Tag, title: &str) -> bool {
|
||||
for track in &self.tracks {
|
||||
if title == track.tags.get_key_value(tag).unwrap().1 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
impl MusicCollection for Playlist<'_> {
|
||||
fn title(&self) -> &String {
|
||||
|
@ -25,3 +78,14 @@ impl MusicCollection for Playlist<'_> {
|
|||
self.tracks.clone()
|
||||
}
|
||||
}
|
||||
impl Default for Playlist<'_> {
|
||||
fn default() -> Self {
|
||||
Playlist {
|
||||
title: String::default(),
|
||||
cover: None,
|
||||
tracks: Vec::default(),
|
||||
play_count: -1,
|
||||
play_time: Duration::zero(),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue