From 3ad8b78e9dc760ca11eb0001a0347272ceb13c12 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sun, 11 Feb 2024 15:54:11 -0500 Subject: [PATCH 01/28] started work on controller protoype --- src/music_controller/controller.rs | 206 ++++++++++++++++++- src/music_storage/db_reader/itunes/reader.rs | 21 +- src/music_storage/playlist.rs | 12 +- 3 files changed, 226 insertions(+), 13 deletions(-) diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index 7f62871..98c3ca8 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -2,25 +2,219 @@ //! player. It manages queues, playback, library access, and //! other functions +use std::path::PathBuf; use std::sync::{Arc, RwLock}; +use std::time::Duration; +use crossbeam_channel::{Sender, Receiver}; +// use std::sync::mpsc; +use crossbeam_channel; +use gstreamer::format::Default; +use gstreamer::query::Uri; +use std::thread::{self, sleep, spawn}; +use std::error::Error; +use crossbeam_channel::unbounded; +use rayon::iter::Rev; +use uuid::Uuid; + +use crate::config; +use crate::music_storage::library::Tag; +use crate::music_storage::playlist::Playlist; use crate::{ music_player::Player, - music_storage::library::Song, - config::config::Config + music_storage::{ + library::{MusicLibrary, Song} + }, + config::config::Config, }; struct Queue { player: Player, name: String, - songs: Vec, + songs: Playlist, +} +impl Queue { + fn new() -> Result> { + Ok( + Queue { + player: Player::new()?, + name: String::new(), + songs: Playlist::new() + } + ) + } } pub struct Controller { - queues: Vec, + // queues: Vec, config: Arc>, + // library: MusicLibrary, + controller_mail: MailMan, + db_mail: MailMan, + queue_mail: Vec>, +} +#[derive(Debug)] + +pub enum ControllerCommand { + Default, + Test +} +#[derive(Debug)] + +enum ControllerResponse { + Empty, + QueueMailMan(MailMan), + +} +#[derive(Debug)] + +pub enum DatabaseCommand { + Default, + Test, + GetSongs, + +} +#[derive(Debug)] + +enum DatabaseResponse { + Empty, + Songs(Vec), +} +#[derive(Debug)] +enum QueueCommand { + Default, + Test, + Play, + Pause, +} +#[derive(Debug)] +enum QueueResponse { + Default, + Test, } -impl Controller { - // more stuff to come +#[derive(Debug)] + +struct MailMan { + pub tx: Sender, + rx: Receiver } +impl MailMan { + pub fn new() -> Self { + let (tx, rx) = unbounded::(); + MailMan { tx, rx } + } +} +impl MailMan { + pub fn double() -> (MailMan, MailMan) { + let (tx, rx) = unbounded::(); + let (tx1, rx1) = unbounded::(); + + ( + MailMan { tx, rx: rx1 }, + MailMan { tx: tx1, rx } + ) + } + + pub fn send(&self, mail: T) -> Result<(), Box> { + &self.tx.send(mail).unwrap(); + Ok(()) + } + + pub fn recv(&self) -> Result> { + let u = self.rx.recv().unwrap(); + Ok(u) + } +} + +#[allow(unused_variables)] +impl Controller { + pub fn start(config: PathBuf) -> Result> { + let config = Config::read_file(config)?; + let uuid = config.libraries.get_default()?.uuid; + + let config = Arc::new(RwLock::from(config)); + let lib = MusicLibrary::init(config.clone(), uuid)?; + + let (out_thread_controller, in_thread) = MailMan::double(); + let monitor_thread = spawn(move || { + use ControllerCommand::*; + loop { + let command = in_thread.recv().unwrap(); + + match command { + Default => (), + Test => { + in_thread.send(ControllerResponse::Empty).unwrap(); + }, + } + } + }); + + + let (out_thread_db, in_thread) = MailMan::double(); + let db_monitor = spawn(move || { + use DatabaseCommand::*; + loop { + let command = in_thread.recv().unwrap(); + + match command { + Default => {}, + Test => { + in_thread.send(DatabaseResponse::Empty).unwrap(); + }, + GetSongs => { + let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect(); + in_thread.send(DatabaseResponse::Songs(songs)).unwrap(); + }, + + } + } + }); + + + + Ok( + Controller { + // queues: Vec::new(), + config, + controller_mail: out_thread_controller, + db_mail: out_thread_db, + queue_mail: Vec::new(), + } + ) + } + fn get_db_songs(&self) -> Vec { + self.db_mail.send(DatabaseCommand::GetSongs); + match self.db_mail.recv().unwrap() { + DatabaseResponse::Songs(songs) => songs, + _ => Vec::new() + } + + } + pub fn new_queue(&mut self) { + let (out_thread_queue, in_thread) = MailMan::::double(); + let queues_monitor = spawn(move || { + use QueueCommand::*; + loop { + let command = in_thread.recv().unwrap(); + match command { + Default => {}, + Test => {}, + Play => {}, + Pause => {}, + } + } + }); + self.queue_mail.push(out_thread_queue); + } +} + +#[test] +fn name() { + let a = Controller::start(PathBuf::from("test-config/config_test.json")).unwrap(); + // sleep(Duration::from_millis(5000)); + _ = a.controller_mail.send(ControllerCommand::Test); + // dbg!(a.get_db_songs()); + // sleep(Duration::from_secs(6)); +} \ No newline at end of file diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs index c2063c0..3bc73f6 100644 --- a/src/music_storage/db_reader/itunes/reader.rs +++ b/src/music_storage/db_reader/itunes/reader.rs @@ -2,17 +2,20 @@ use file_format::FileFormat; use lofty::{AudioFile, LoftyError, ParseOptions, Probe, TagType, TaggedFileExt}; use quick_xml::events::Event; use quick_xml::reader::Reader; +use uuid::Uuid; use std::collections::{BTreeMap, HashMap}; use std::fs::File; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::sync::{Arc, RwLock}; use std::time::Duration as StdDur; use std::vec::Vec; use chrono::prelude::*; +use crate::config::config::{Config, ConfigLibrary}; use crate::music_storage::db_reader::extern_library::ExternalLibrary; -use crate::music_storage::library::{AlbumArt, Service, Song, Tag, URI}; +use crate::music_storage::library::{AlbumArt, MusicLibrary, Service, Song, Tag, URI}; use crate::music_storage::utils; use urlencoding::decode; @@ -320,4 +323,20 @@ impl ITunesSong { // println!("{:.2?}", song); Ok(song) } +} + +#[test] +fn itunes_lib_test() { + let mut config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); + let config_lib = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None); + config.libraries.libraries.push(config_lib.clone()); + + let songs = ITunesLibrary::from_file(Path::new("test-config\\iTunesLib.xml")).to_songs(); + + let mut library = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), config_lib.uuid).unwrap(); + + songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap()); + + config.write_file().unwrap(); + library.save(config).unwrap(); } \ No newline at end of file diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs index c2c6b74..69ae390 100644 --- a/src/music_storage/playlist.rs +++ b/src/music_storage/playlist.rs @@ -23,15 +23,15 @@ pub enum SortOrder { Tag(Tag) } #[derive(Debug, Clone)] -pub struct Playlist<'a> { +pub struct Playlist { title: String, - cover: Option<&'a AlbumArt>, + cover: Option, tracks: Vec, sort_order: SortOrder, play_count: i32, play_time: Duration, } -impl<'a> Playlist<'a> { +impl Playlist { pub fn new() -> Self { Default::default() } @@ -109,7 +109,7 @@ impl<'a> Playlist<'a> { .unwrap(); m3u8.write_to(&mut file).unwrap(); } - pub fn from_m3u8(path: &str) -> Result, Error> { + pub fn from_m3u8(path: &str) -> Result { let mut file = match File::open(path) { Ok(file) => file, Err(e) => return Err(e), @@ -137,7 +137,7 @@ impl<'a> Playlist<'a> { &self.title } fn cover(&self) -> Option<&AlbumArt> { - match self.cover { + match &self.cover { Some(e) => Some(e), None => None, } @@ -149,7 +149,7 @@ impl<'a> Playlist<'a> { -impl Default for Playlist<'_> { +impl Default for Playlist { fn default() -> Self { Playlist { title: String::default(), From 599ddc584c6e5041f107c543a06aba82ece6def4 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Mon, 12 Feb 2024 13:43:30 -0500 Subject: [PATCH 02/28] Finished Controller Prototype --- .gitignore | 2 + src/config/config.rs | 6 +- src/music_controller/controller.rs | 120 ++++++++++++++++--- src/music_storage/db_reader/foobar/reader.rs | 3 + src/music_storage/db_reader/itunes/reader.rs | 1 + src/music_storage/library.rs | 32 ++++- 6 files changed, 139 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index a9e4c0b..7a0594f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ music_database* *.m3u *.m3u8 *.json +*.zip +*.xml diff --git a/src/config/config.rs b/src/config/config.rs index 9314d65..15b7121 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -71,8 +71,10 @@ impl ConfigLibraries { } pub fn get_library(&self, uuid: &Uuid) -> Result { + dbg!(&uuid); for library in &self.libraries { if &library.uuid == uuid { + dbg!(&library.uuid); return Ok(library.to_owned()) } } @@ -128,8 +130,8 @@ impl Config { let mut file: File = File::open(path)?; let mut bun: String = String::new(); _ = file.read_to_string(&mut bun); - let ny: Config = serde_json::from_str::(&bun)?; - Ok(ny) + let config: Config = serde_json::from_str::(&bun)?; + Ok(config) } } diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index 98c3ca8..da2ea0f 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -2,7 +2,7 @@ //! player. It manages queues, playback, library access, and //! other functions -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; use std::time::Duration; use crossbeam_channel::{Sender, Receiver}; @@ -18,7 +18,7 @@ use rayon::iter::Rev; use uuid::Uuid; use crate::config; -use crate::music_storage::library::Tag; +use crate::music_storage::library::{Tag, URI}; use crate::music_storage::playlist::Playlist; use crate::{ music_player::Player, @@ -31,7 +31,7 @@ use crate::{ struct Queue { player: Player, name: String, - songs: Playlist, + songs: Vec, } impl Queue { fn new() -> Result> { @@ -39,10 +39,15 @@ impl Queue { Queue { player: Player::new()?, name: String::new(), - songs: Playlist::new() + songs: Vec::new() } ) } + fn set_tracks(&mut self, tracks: Vec) { + let mut tracks = tracks; + self.songs.clear(); + self.songs.append(&mut tracks); + } } pub struct Controller { @@ -54,39 +59,47 @@ pub struct Controller { queue_mail: Vec>, } #[derive(Debug)] - pub enum ControllerCommand { Default, Test } -#[derive(Debug)] +#[derive(Debug)] enum ControllerResponse { Empty, QueueMailMan(MailMan), } -#[derive(Debug)] +#[derive(Debug)] pub enum DatabaseCommand { Default, Test, GetSongs, + QueryUuid(Uuid), + QueryUuids(Vec), + ReadFolder(String), } -#[derive(Debug)] +#[derive(Debug)] enum DatabaseResponse { Empty, + Song(Song), Songs(Vec), } + #[derive(Debug)] enum QueueCommand { Default, Test, Play, Pause, + SetSongs(Vec), + // SetLocation(URI), + Enqueue(URI), } + #[derive(Debug)] enum QueueResponse { Default, @@ -94,11 +107,11 @@ enum QueueResponse { } #[derive(Debug)] - struct MailMan { pub tx: Sender, rx: Receiver } + impl MailMan { pub fn new() -> Self { let (tx, rx) = unbounded::(); @@ -129,12 +142,13 @@ impl MailMan { #[allow(unused_variables)] impl Controller { - pub fn start(config: PathBuf) -> Result> { - let config = Config::read_file(config)?; + pub fn start(config_path: String) -> Result> { + let config_path = PathBuf::from(config_path); + let config = Config::read_file(config_path)?; let uuid = config.libraries.get_default()?.uuid; let config = Arc::new(RwLock::from(config)); - let lib = MusicLibrary::init(config.clone(), uuid)?; + let mut lib = MusicLibrary::init(config.clone(), uuid)?; let (out_thread_controller, in_thread) = MailMan::double(); let monitor_thread = spawn(move || { @@ -167,6 +181,26 @@ impl Controller { let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect(); in_thread.send(DatabaseResponse::Songs(songs)).unwrap(); }, + QueryUuid(uuid) => { + match lib.query_uuid(&uuid) { + Some(song) => in_thread.send(DatabaseResponse::Song(song.0.clone())).unwrap(), + None => in_thread.send(DatabaseResponse::Empty).unwrap(), + } + }, + QueryUuids(uuids) => { + let mut vec = Vec::new(); + for uuid in uuids { + match lib.query_uuid(&uuid) { + Some(song) => vec.push(song.0.clone()), + None => unimplemented!() + } + } + in_thread.send(DatabaseResponse::Songs(vec)).unwrap(); + }, + ReadFolder(folder) => { + lib.scan_folder(&folder).unwrap(); + in_thread.send(DatabaseResponse::Empty).unwrap(); + } } } @@ -184,6 +218,7 @@ impl Controller { } ) } + fn get_db_songs(&self) -> Vec { self.db_mail.send(DatabaseCommand::GetSongs); match self.db_mail.recv().unwrap() { @@ -192,29 +227,76 @@ impl Controller { } } + pub fn new_queue(&mut self) { let (out_thread_queue, in_thread) = MailMan::::double(); let queues_monitor = spawn(move || { use QueueCommand::*; + let mut queue = Queue::new().unwrap(); loop { let command = in_thread.recv().unwrap(); match command { Default => {}, - Test => {}, - Play => {}, + Test => { in_thread.send(QueueResponse::Test).unwrap() }, + Play => { + queue.player.play().unwrap(); + in_thread.send(QueueResponse::Default).unwrap(); + }, Pause => {}, + SetSongs(songs) => { + queue.set_tracks(songs); + in_thread.send(QueueResponse::Default).unwrap(); + }, + Enqueue(uri) => { + queue.player.enqueue_next(&uri); + } } } }); self.queue_mail.push(out_thread_queue); } + + fn play(&self, index: usize) -> Result<(), Box> { + let mail = &self.queue_mail[index]; + mail.send(QueueCommand::Play)?; + dbg!(mail.recv()?); + Ok(()) + } + + fn set_songs(&self, index: usize, songs: Vec) -> Result<(), Box> { + let mail = &self.queue_mail[index]; + mail.send(QueueCommand::SetSongs(songs))?; + dbg!(mail.recv()?); + Ok(()) + } + + fn enqueue(&self, index: usize, uri: URI) -> Result<(), Box> { + let mail = &self.queue_mail[index]; + mail.send(QueueCommand::Enqueue(uri))?; + dbg!(mail.recv()?); + Ok(()) + } + fn scan_folder(&self, folder: String) -> Result<(), Box> { + let mail = &self.db_mail; + mail.send(DatabaseCommand::ReadFolder(folder))?; + dbg!(mail.recv()?); + Ok(()) + } + } #[test] fn name() { - let a = Controller::start(PathBuf::from("test-config/config_test.json")).unwrap(); - // sleep(Duration::from_millis(5000)); - _ = a.controller_mail.send(ControllerCommand::Test); - // dbg!(a.get_db_songs()); - // sleep(Duration::from_secs(6)); + let mut a = match Controller::start("test-config/config_test.json".to_string()) { + Ok(c) => c, + Err(e) => panic!("{e}") + }; + sleep(Duration::from_millis(500)); + a.scan_folder("test-config/music/".to_string()); + a.new_queue(); + let songs = a.get_db_songs(); + a.enqueue(0, songs[0].location.clone()); + a.play(0).unwrap(); + + sleep(Duration::from_secs(10)); } \ No newline at end of file diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs index 3cb417d..324b863 100644 --- a/src/music_storage/db_reader/foobar/reader.rs +++ b/src/music_storage/db_reader/foobar/reader.rs @@ -1,6 +1,8 @@ use std::collections::BTreeMap; use std::{fs::File, io::Read, path::Path, time::Duration}; +use uuid::Uuid; + use super::utils::meta_offset; use crate::music_storage::db_reader::common::{get_bytes, get_bytes_vec}; use crate::music_storage::db_reader::extern_library::ExternalLibrary; @@ -177,6 +179,7 @@ impl FoobarPlaylistTrack { Song { location, + uuid: Uuid::new_v4(), plays: 0, skips: 0, favorited: false, diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs index 3bc73f6..15f89a9 100644 --- a/src/music_storage/db_reader/itunes/reader.rs +++ b/src/music_storage/db_reader/itunes/reader.rs @@ -170,6 +170,7 @@ impl ExternalLibrary for ITunesLibrary { let ny: Song = Song { location: sug, + uuid: Uuid::new_v4(), plays: track.plays, skips: 0, favorited: track.favorited, diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index 502429b..e788639 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -119,6 +119,7 @@ impl ToString for Field { #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct Song { pub location: URI, + pub uuid: Uuid, pub plays: i32, pub skips: i32, pub favorited: bool, @@ -256,6 +257,7 @@ impl Song { let new_song = Song { location: URI::Local(binding), + uuid: Uuid::new_v4(), plays: 0, skips: 0, favorited: false, @@ -378,6 +380,7 @@ impl Song { start, end, }, + uuid: Uuid::new_v4(), plays: 0, skips: 0, favorited: false, @@ -552,7 +555,7 @@ pub struct MusicLibrary { #[test] fn library_init() { - let config = Config::read_file(PathBuf::from("config_test.json")).unwrap(); + let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap(); let target_uuid = config.libraries.libraries[0].uuid; let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap(); dbg!(a); @@ -575,13 +578,14 @@ impl MusicLibrary { /// the [MusicLibrary] Vec pub fn init(config: Arc>, uuid: Uuid) -> Result> { let global_config = &*config.read().unwrap(); + let path = global_config.libraries.get_library(&uuid)?.path; - let library: MusicLibrary = match global_config.libraries.get_library(&uuid)?.path.exists() { - true => read_file(global_config.libraries.get_library(&uuid)?.path)?, + let library: MusicLibrary = match path.exists() { + true => read_file(path)?, false => { // If the library does not exist, re-create it let lib = MusicLibrary::new(String::new(), uuid); - write_file(&lib, global_config.libraries.get_library(&uuid)?.path)?; + write_file(&lib, path)?; lib } }; @@ -643,6 +647,26 @@ impl MusicLibrary { } } + /// Queries for a [Song] by its [Uuid], returning a single `Song` + /// with the `Uuid` that matches along with its position in the library + pub fn query_uuid(&self, uuid: &Uuid) -> Option<(&Song, usize)> { + let result = self + .library + .par_iter() + .enumerate() + .try_for_each(|(i, track)| { + if uuid == &track.uuid { + return std::ops::ControlFlow::Break((track, i)); + } + Continue(()) + }); + + match result { + Break(song) => Some(song), + Continue(_) => None, + } + } + /// Queries for a [Song] by its [PathBuf], returning a `Vec<&Song>` /// with matching `PathBuf`s fn query_path(&self, path: PathBuf) -> Option> { From e25a7bfcc8862ca5fc5b643b0738b04ae1c2a193 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Mon, 12 Feb 2024 14:12:51 -0500 Subject: [PATCH 03/28] playing 2 queues at once works --- src/music_controller/controller.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index da2ea0f..d887ccd 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -249,6 +249,7 @@ impl Controller { }, Enqueue(uri) => { queue.player.enqueue_next(&uri); + // in_thread.send(QueueResponse::Default).unwrap(); } } } @@ -273,7 +274,7 @@ impl Controller { fn enqueue(&self, index: usize, uri: URI) -> Result<(), Box> { let mail = &self.queue_mail[index]; mail.send(QueueCommand::Enqueue(uri))?; - dbg!(mail.recv()?); + // dbg!(mail.recv()?); Ok(()) } fn scan_folder(&self, folder: String) -> Result<(), Box> { @@ -294,9 +295,12 @@ fn name() { sleep(Duration::from_millis(500)); a.scan_folder("test-config/music/".to_string()); a.new_queue(); + a.new_queue(); let songs = a.get_db_songs(); - a.enqueue(0, songs[0].location.clone()); + a.enqueue(0, songs[1].location.clone()); + a.enqueue(1, songs[2].location.clone()); a.play(0).unwrap(); + a.play(1).unwrap(); sleep(Duration::from_secs(10)); } \ No newline at end of file From c842bf0b9cfabadab4e915f625198cd6ea73f3ef Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Fri, 16 Feb 2024 00:26:07 -0500 Subject: [PATCH 04/28] edited the Play command and the test function --- src/music_controller/controller.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index d887ccd..777b7e2 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -239,8 +239,11 @@ impl Controller { Default => {}, Test => { in_thread.send(QueueResponse::Test).unwrap() }, Play => { - queue.player.play().unwrap(); - in_thread.send(QueueResponse::Default).unwrap(); + match queue.player.play() { + Ok(_) => in_thread.send(QueueResponse::Default).unwrap(), + Err(_) => unimplemented!() + }; + }, Pause => {}, SetSongs(songs) => { @@ -248,7 +251,10 @@ impl Controller { in_thread.send(QueueResponse::Default).unwrap(); }, Enqueue(uri) => { - queue.player.enqueue_next(&uri); + if uri.exists().unwrap() { + queue.player.enqueue_next(&uri); + } + // in_thread.send(QueueResponse::Default).unwrap(); } } @@ -295,12 +301,12 @@ fn name() { sleep(Duration::from_millis(500)); a.scan_folder("test-config/music/".to_string()); a.new_queue(); - a.new_queue(); + // a.new_queue(); let songs = a.get_db_songs(); - a.enqueue(0, songs[1].location.clone()); - a.enqueue(1, songs[2].location.clone()); + a.enqueue(0, songs[4].location.clone()); + // a.enqueue(1, songs[2].location.clone()); a.play(0).unwrap(); - a.play(1).unwrap(); + // a.play(1).unwrap(); sleep(Duration::from_secs(10)); -} \ No newline at end of file +} From 3c9311003731292c8f2a7025e9959ba9b1a5fc2c Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Fri, 16 Feb 2024 00:56:38 -0500 Subject: [PATCH 05/28] Added #![allow(unused)] --- src/lib.rs | 2 ++ src/music_controller/controller.rs | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3119019..41894fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + pub mod music_storage { pub mod library; pub mod music_collection; diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index 777b7e2..11a6fd4 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -22,9 +22,7 @@ use crate::music_storage::library::{Tag, URI}; use crate::music_storage::playlist::Playlist; use crate::{ music_player::Player, - music_storage::{ - library::{MusicLibrary, Song} - }, + music_storage::library::{MusicLibrary, Song}, config::config::Config, }; From c6b561c7af509411f01bbf1baa5b701dc215abb3 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Fri, 16 Feb 2024 22:24:02 -0500 Subject: [PATCH 06/28] made prepatory changes to the library --- src/config/config.rs | 31 ++++++++++++++---- src/music_controller/controller.rs | 34 ++++++++++---------- src/music_storage/db_reader/foobar/reader.rs | 1 + src/music_storage/db_reader/itunes/reader.rs | 11 +++++-- src/music_storage/library.rs | 25 +++++++++++++- 5 files changed, 75 insertions(+), 27 deletions(-) diff --git a/src/config/config.rs b/src/config/config.rs index 15b7121..918af12 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -4,7 +4,7 @@ use std::{ io::{Error, Write, Read}, sync::{Arc, RwLock}, }; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use serde_json::to_string_pretty; use thiserror::Error; use uuid::Uuid; @@ -126,6 +126,23 @@ impl Config { Ok(()) } + pub fn save_backup(&self) -> Result<(), Box> { + match &self.backup_folder { + Some(path) => { + let mut writer = path.clone(); + writer.set_extension("tmp"); + let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open(&writer)?; + let config = to_string_pretty(self)?; + // dbg!(&config); + + file.write_all(config.as_bytes())?; + fs::rename(writer, self.path.as_path())?; + Ok(()) + }, + None => Err(ConfigError::NoBackupLibrary.into()) + } + } + pub fn read_file(path: PathBuf) -> Result { let mut file: File = File::open(path)?; let mut bun: String = String::new(); @@ -144,6 +161,8 @@ pub enum ConfigError { //TODO: do something about playlists #[error("Please provide a better m3u8 Playlist")] BadPlaylist, + #[error("No backup Config folder present")] + NoBackupLibrary, } @@ -166,16 +185,16 @@ fn config_test() { }; config.write_file(); let arc = Arc::new(RwLock::from(config)); - MusicLibrary::init(arc.clone(), lib_a.uuid.clone()).unwrap(); - MusicLibrary::init(arc.clone(), lib_b.uuid.clone()).unwrap(); - MusicLibrary::init(arc.clone(), lib_c.uuid.clone()).unwrap(); + MusicLibrary::init(arc.clone(), lib_a.uuid).unwrap(); + MusicLibrary::init(arc.clone(), lib_b.uuid).unwrap(); + MusicLibrary::init(arc.clone(), lib_c.uuid).unwrap(); } #[test] fn test2() { let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); - let uuid = config.libraries.get_default().unwrap().uuid.clone(); + let uuid = config.libraries.get_default().unwrap().uuid; let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap(); lib.scan_folder("test-config/music/").unwrap(); lib.save(config.clone()).unwrap(); @@ -187,7 +206,7 @@ fn test2() { fn test3() { let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); let uuid = config.libraries.get_default().unwrap().uuid; - let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap(); + let lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap(); dbg!(lib); } \ No newline at end of file diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index 11a6fd4..750ee02 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -52,12 +52,12 @@ pub struct Controller { // queues: Vec, config: Arc>, // library: MusicLibrary, - controller_mail: MailMan, - db_mail: MailMan, - queue_mail: Vec>, + controller_mail: MailMan, + db_mail: MailMan, + queue_mail: Vec>, } #[derive(Debug)] -pub enum ControllerCommand { +pub enum ControllerCmd { Default, Test } @@ -65,12 +65,12 @@ pub enum ControllerCommand { #[derive(Debug)] enum ControllerResponse { Empty, - QueueMailMan(MailMan), + QueueMailMan(MailMan), } #[derive(Debug)] -pub enum DatabaseCommand { +pub enum DatabaseCmd { Default, Test, GetSongs, @@ -88,7 +88,7 @@ enum DatabaseResponse { } #[derive(Debug)] -enum QueueCommand { +enum QueueCmd { Default, Test, Play, @@ -128,7 +128,7 @@ impl MailMan { } pub fn send(&self, mail: T) -> Result<(), Box> { - &self.tx.send(mail).unwrap(); + self.tx.send(mail).unwrap(); Ok(()) } @@ -150,7 +150,7 @@ impl Controller { let (out_thread_controller, in_thread) = MailMan::double(); let monitor_thread = spawn(move || { - use ControllerCommand::*; + use ControllerCmd::*; loop { let command = in_thread.recv().unwrap(); @@ -166,7 +166,7 @@ impl Controller { let (out_thread_db, in_thread) = MailMan::double(); let db_monitor = spawn(move || { - use DatabaseCommand::*; + use DatabaseCmd::*; loop { let command = in_thread.recv().unwrap(); @@ -218,7 +218,7 @@ impl Controller { } fn get_db_songs(&self) -> Vec { - self.db_mail.send(DatabaseCommand::GetSongs); + self.db_mail.send(DatabaseCmd::GetSongs); match self.db_mail.recv().unwrap() { DatabaseResponse::Songs(songs) => songs, _ => Vec::new() @@ -227,9 +227,9 @@ impl Controller { } pub fn new_queue(&mut self) { - let (out_thread_queue, in_thread) = MailMan::::double(); + let (out_thread_queue, in_thread) = MailMan::::double(); let queues_monitor = spawn(move || { - use QueueCommand::*; + use QueueCmd::*; let mut queue = Queue::new().unwrap(); loop { let command = in_thread.recv().unwrap(); @@ -263,27 +263,27 @@ impl Controller { fn play(&self, index: usize) -> Result<(), Box> { let mail = &self.queue_mail[index]; - mail.send(QueueCommand::Play)?; + mail.send(QueueCmd::Play)?; dbg!(mail.recv()?); Ok(()) } fn set_songs(&self, index: usize, songs: Vec) -> Result<(), Box> { let mail = &self.queue_mail[index]; - mail.send(QueueCommand::SetSongs(songs))?; + mail.send(QueueCmd::SetSongs(songs))?; dbg!(mail.recv()?); Ok(()) } fn enqueue(&self, index: usize, uri: URI) -> Result<(), Box> { let mail = &self.queue_mail[index]; - mail.send(QueueCommand::Enqueue(uri))?; + mail.send(QueueCmd::Enqueue(uri))?; // dbg!(mail.recv()?); Ok(()) } fn scan_folder(&self, folder: String) -> Result<(), Box> { let mail = &self.db_mail; - mail.send(DatabaseCommand::ReadFolder(folder))?; + mail.send(DatabaseCmd::ReadFolder(folder))?; dbg!(mail.recv()?); Ok(()) } diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs index 324b863..815f9a7 100644 --- a/src/music_storage/db_reader/foobar/reader.rs +++ b/src/music_storage/db_reader/foobar/reader.rs @@ -183,6 +183,7 @@ impl FoobarPlaylistTrack { plays: 0, skips: 0, favorited: false, + // banned: None, rating: None, format: None, duration: self.duration, diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs index 15f89a9..decf390 100644 --- a/src/music_storage/db_reader/itunes/reader.rs +++ b/src/music_storage/db_reader/itunes/reader.rs @@ -15,7 +15,7 @@ use chrono::prelude::*; use crate::config::config::{Config, ConfigLibrary}; use crate::music_storage::db_reader::extern_library::ExternalLibrary; -use crate::music_storage::library::{AlbumArt, MusicLibrary, Service, Song, Tag, URI}; +use crate::music_storage::library::{AlbumArt, MusicLibrary, Service, Song, Tag, URI, BannedType}; use crate::music_storage::utils; use urlencoding::decode; @@ -149,7 +149,7 @@ impl ExternalLibrary for ITunesLibrary { continue; } - let sug: URI = if track.location.contains("file://localhost/") { + let location: URI = if track.location.contains("file://localhost/") { URI::Local(PathBuf::from( decode(track.location.strip_prefix("file://localhost/").unwrap()) .unwrap() @@ -169,11 +169,16 @@ impl ExternalLibrary for ITunesLibrary { let play_time_ = StdDur::from_secs(track.plays as u64 * dur.as_secs()); let ny: Song = Song { - location: sug, + location, uuid: Uuid::new_v4(), plays: track.plays, skips: 0, favorited: track.favorited, + // banned: if track.banned { + // Some(BannedType::All) + // }else { + // None + // }, rating: track.rating, format: match FileFormat::from_file(PathBuf::from(&loc)) { Ok(e) => Some(e), diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index e788639..93b9991 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -115,6 +115,26 @@ impl ToString for Field { } } +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub enum BannedType { + Shuffle, + All, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub enum DoNotTrack { + // TODO: add services to not track +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +enum SongType { + // TODO: add song types + Main, + Instrumental, + Remix, + Custom(String) +} + /// Stores information about a single song #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct Song { @@ -123,6 +143,7 @@ pub struct Song { pub plays: i32, pub skips: i32, pub favorited: bool, + // pub banned: Option, pub rating: Option, pub format: Option, pub duration: Duration, @@ -261,6 +282,7 @@ impl Song { plays: 0, skips: 0, favorited: false, + // banned: None, rating: None, format, duration, @@ -384,6 +406,7 @@ impl Song { plays: 0, skips: 0, favorited: false, + // banned: None, rating: None, format, duration, @@ -397,7 +420,7 @@ impl Song { tracks.push((new_song, audio_location.clone())); } } - Ok((tracks)) + Ok(tracks) } } From d2822bbb37a3ffb772e4c3f161a21e4dc2b2ab97 Mon Sep 17 00:00:00 2001 From: G2-Games Date: Sat, 17 Feb 2024 21:30:53 -0600 Subject: [PATCH 07/28] Moved location of `uri.exists()` to in `Player` --- src/music_controller/controller.rs | 4 +--- src/music_player.rs | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index 750ee02..d6d1398 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -249,9 +249,7 @@ impl Controller { in_thread.send(QueueResponse::Default).unwrap(); }, Enqueue(uri) => { - if uri.exists().unwrap() { - queue.player.enqueue_next(&uri); - } + queue.player.enqueue_next(&uri).unwrap(); // in_thread.send(QueueResponse::Default).unwrap(); } diff --git a/src/music_player.rs b/src/music_player.rs index 4e7de23..12af300 100644 --- a/src/music_player.rs +++ b/src/music_player.rs @@ -68,6 +68,8 @@ pub enum PlayerError { Factory(#[from] glib::BoolError), #[error("could not change playback state")] StateChange(#[from] gst::StateChangeError), + #[error("the file or source is not found")] + NotFound, #[error("failed to build gstreamer item")] Build, #[error("poison error")] @@ -257,12 +259,17 @@ impl Player { &self.source } - pub fn enqueue_next(&mut self, next_track: &URI) { - self.set_source(next_track); + pub fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError> { + self.set_source(next_track) } /// Set the playback URI - fn set_source(&mut self, source: &URI) { + fn set_source(&mut self, source: &URI) -> Result<(), PlayerError> { + if !source.exists().is_ok_and(|x| x) { + // If the source doesn't exist, gstreamer will crash! + return Err(PlayerError::NotFound) + } + // Make sure the playback tracker knows the stuff is stopped self.playback_tx.send(PlaybackStats::Switching).unwrap(); @@ -290,7 +297,7 @@ impl Player { let now = std::time::Instant::now(); while now.elapsed() < std::time::Duration::from_millis(20) { if self.seek_to(Duration::from_std(*start).unwrap()).is_ok() { - return; + return Ok(()); } std::thread::sleep(std::time::Duration::from_millis(1)); } @@ -321,6 +328,8 @@ impl Player { }).unwrap(); } } + + Ok(()) } /// Gets a mutable reference to the playbin element From 74e2933de19609ee90f88f3c16852421d7c4723c Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Fri, 23 Feb 2024 20:51:59 -0500 Subject: [PATCH 08/28] Added more functions for testing --- src/music_controller/controller.rs | 101 ++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index d6d1398..2d6a383 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -41,6 +41,7 @@ impl Queue { } ) } + fn set_tracks(&mut self, tracks: Vec) { let mut tracks = tracks; self.songs.clear(); @@ -73,6 +74,7 @@ enum ControllerResponse { pub enum DatabaseCmd { Default, Test, + SaveLibrary, GetSongs, QueryUuid(Uuid), QueryUuids(Vec), @@ -85,6 +87,7 @@ enum DatabaseResponse { Empty, Song(Song), Songs(Vec), + Library(MusicLibrary), } #[derive(Debug)] @@ -96,12 +99,14 @@ enum QueueCmd { SetSongs(Vec), // SetLocation(URI), Enqueue(URI), + SetVolume(f64), } #[derive(Debug)] enum QueueResponse { Default, Test, + Index(i32), } #[derive(Debug)] @@ -145,9 +150,10 @@ impl Controller { let config = Config::read_file(config_path)?; let uuid = config.libraries.get_default()?.uuid; - let config = Arc::new(RwLock::from(config)); - let mut lib = MusicLibrary::init(config.clone(), uuid)?; + let config_ = Arc::new(RwLock::from(config)); + let mut lib = MusicLibrary::init(config_.clone(), uuid)?; + let config = config_.clone(); let (out_thread_controller, in_thread) = MailMan::double(); let monitor_thread = spawn(move || { use ControllerCmd::*; @@ -163,7 +169,7 @@ impl Controller { } }); - + let config = config_.clone(); let (out_thread_db, in_thread) = MailMan::double(); let db_monitor = spawn(move || { use DatabaseCmd::*; @@ -179,6 +185,10 @@ impl Controller { let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect(); in_thread.send(DatabaseResponse::Songs(songs)).unwrap(); }, + SaveLibrary => { + //TODO: make this send lib ref to the function to save instead + lib.save(config.read().unwrap().to_owned()).unwrap(); + }, QueryUuid(uuid) => { match lib.query_uuid(&uuid) { Some(song) => in_thread.send(DatabaseResponse::Song(song.0.clone())).unwrap(), @@ -209,7 +219,7 @@ impl Controller { Ok( Controller { // queues: Vec::new(), - config, + config: config_.clone(), controller_mail: out_thread_controller, db_mail: out_thread_db, queue_mail: Vec::new(), @@ -217,16 +227,27 @@ impl Controller { ) } - fn get_db_songs(&self) -> Vec { + fn lib_get_songs(&self) -> Vec { self.db_mail.send(DatabaseCmd::GetSongs); match self.db_mail.recv().unwrap() { DatabaseResponse::Songs(songs) => songs, _ => Vec::new() } - } - pub fn new_queue(&mut self) { + fn lib_scan_folder(&self, folder: String) -> Result<(), Box> { + let mail = &self.db_mail; + mail.send(DatabaseCmd::ReadFolder(folder))?; + dbg!(mail.recv()?); + Ok(()) + } + + pub fn lib_save(&self) -> Result<(), Box> { + self.db_mail.send(DatabaseCmd::SaveLibrary); + Ok(()) + } + + pub fn q_new(&mut self) -> Result> { let (out_thread_queue, in_thread) = MailMan::::double(); let queues_monitor = spawn(move || { use QueueCmd::*; @@ -239,11 +260,16 @@ impl Controller { Play => { match queue.player.play() { Ok(_) => in_thread.send(QueueResponse::Default).unwrap(), - Err(_) => unimplemented!() + Err(_) => todo!() }; }, - Pause => {}, + Pause => { + match queue.player.pause() { + Ok(_) => in_thread.send(QueueResponse::Default).unwrap(), + Err(_) => todo!() + } + }, SetSongs(songs) => { queue.set_tracks(songs); in_thread.send(QueueResponse::Default).unwrap(); @@ -252,57 +278,84 @@ impl Controller { queue.player.enqueue_next(&uri).unwrap(); // in_thread.send(QueueResponse::Default).unwrap(); + }, + SetVolume(vol) => { + queue.player.set_volume(vol); } } } }); self.queue_mail.push(out_thread_queue); + Ok((self.queue_mail.len() - 1)) } - fn play(&self, index: usize) -> Result<(), Box> { + fn q_play(&self, index: usize) -> Result<(), Box> { let mail = &self.queue_mail[index]; mail.send(QueueCmd::Play)?; dbg!(mail.recv()?); Ok(()) } - fn set_songs(&self, index: usize, songs: Vec) -> Result<(), Box> { + fn q_pause(&self, index: usize) -> Result<(), Box> { + let mail = &self.queue_mail[index]; + mail.send(QueueCmd::Pause)?; + dbg!(mail.recv()?); + Ok(()) + } + + pub fn q_set_volume(&self, index: usize, volume: f64) -> Result<(), Box> { + let mail = &self.queue_mail[index]; + mail.send(QueueCmd::SetVolume(volume))?; + Ok(()) + } + + fn q_set_songs(&self, index: usize, songs: Vec) -> Result<(), Box> { let mail = &self.queue_mail[index]; mail.send(QueueCmd::SetSongs(songs))?; dbg!(mail.recv()?); Ok(()) } - fn enqueue(&self, index: usize, uri: URI) -> Result<(), Box> { + fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box> { let mail = &self.queue_mail[index]; mail.send(QueueCmd::Enqueue(uri))?; // dbg!(mail.recv()?); Ok(()) } - fn scan_folder(&self, folder: String) -> Result<(), Box> { - let mail = &self.db_mail; - mail.send(DatabaseCmd::ReadFolder(folder))?; - dbg!(mail.recv()?); - Ok(()) - } + } #[test] -fn name() { +fn play_test() { let mut a = match Controller::start("test-config/config_test.json".to_string()) { Ok(c) => c, Err(e) => panic!("{e}") }; sleep(Duration::from_millis(500)); - a.scan_folder("test-config/music/".to_string()); - a.new_queue(); + + let i = a.q_new().unwrap(); + a.q_set_volume(i, 0.04); // a.new_queue(); - let songs = a.get_db_songs(); - a.enqueue(0, songs[4].location.clone()); + let songs = a.lib_get_songs(); + a.q_enqueue(i, songs[2].location.clone()); // a.enqueue(1, songs[2].location.clone()); - a.play(0).unwrap(); + a.q_play(i).unwrap(); // a.play(1).unwrap(); sleep(Duration::from_secs(10)); + a.q_pause(i); + sleep(Duration::from_secs(10)); + a.q_play(i); + sleep(Duration::from_secs(1000)); +} + +#[test] +fn test_() { + let a = match Controller::start("test-config/config_test.json".to_string()) { + Ok(c) => c, + Err(e) => panic!("{e}") + }; + a.lib_scan_folder("F:/Music/Mp3".to_string()); + a.lib_save(); } From 0e6a676e09352f73c916da93dcc8ffc23048e38f Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sat, 24 Feb 2024 20:25:56 -0500 Subject: [PATCH 09/28] moved queue to its own file --- src/lib.rs | 1 + src/music_controller/controller.rs | 24 +----------------------- src/music_controller/queue.rs | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 src/music_controller/queue.rs diff --git a/src/lib.rs b/src/lib.rs index 41894fa..a9d0db0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub mod music_storage { pub mod music_controller{ pub mod controller; pub mod connections; + pub mod queue; } pub mod music_player; diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index 2d6a383..42f8f0e 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -24,31 +24,9 @@ use crate::{ music_player::Player, music_storage::library::{MusicLibrary, Song}, config::config::Config, + music_controller::queue::Queue, }; -struct Queue { - player: Player, - name: String, - songs: Vec, -} -impl Queue { - fn new() -> Result> { - Ok( - Queue { - player: Player::new()?, - name: String::new(), - songs: Vec::new() - } - ) - } - - fn set_tracks(&mut self, tracks: Vec) { - let mut tracks = tracks; - self.songs.clear(); - self.songs.append(&mut tracks); - } -} - pub struct Controller { // queues: Vec, config: Arc>, diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs new file mode 100644 index 0000000..7eaace7 --- /dev/null +++ b/src/music_controller/queue.rs @@ -0,0 +1,25 @@ +use crate::{music_player::Player, music_storage::library::Song}; +use std::error::Error; + +pub struct Queue { + pub player: Player, + pub name: String, + pub songs: Vec, +} +impl Queue { + pub fn new() -> Result> { + Ok( + Queue { + player: Player::new()?, + name: String::new(), + songs: Vec::new() + } + ) + } + + pub fn set_tracks(&mut self, tracks: Vec) { + let mut tracks = tracks; + self.songs.clear(); + self.songs.append(&mut tracks); + } +} From 79ec5aa1ef3e2ba9d3c0e810a06fc8e9186805ff Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Mon, 26 Feb 2024 20:17:37 -0500 Subject: [PATCH 10/28] Added function to add items to the queue. currently does not work with songs that have the `Played` State --- src/music_controller/controller.rs | 24 ++++---- src/music_controller/queue.rs | 97 +++++++++++++++++++++++++++--- src/music_player.rs | 1 + 3 files changed, 102 insertions(+), 20 deletions(-) diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index 42f8f0e..8d7f08e 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -27,6 +27,8 @@ use crate::{ music_controller::queue::Queue, }; +use super::queue::{QueueItem, QueueState}; + pub struct Controller { // queues: Vec, config: Arc>, @@ -74,7 +76,7 @@ enum QueueCmd { Test, Play, Pause, - SetSongs(Vec), + // SetSongs(Vec>), // SetLocation(URI), Enqueue(URI), SetVolume(f64), @@ -248,10 +250,10 @@ impl Controller { Err(_) => todo!() } }, - SetSongs(songs) => { - queue.set_tracks(songs); - in_thread.send(QueueResponse::Default).unwrap(); - }, + // SetSongs(songs) => { + // queue.set_tracks(songs); + // in_thread.send(QueueResponse::Default).unwrap(); + // }, Enqueue(uri) => { queue.player.enqueue_next(&uri).unwrap(); @@ -287,12 +289,12 @@ impl Controller { Ok(()) } - fn q_set_songs(&self, index: usize, songs: Vec) -> Result<(), Box> { - let mail = &self.queue_mail[index]; - mail.send(QueueCmd::SetSongs(songs))?; - dbg!(mail.recv()?); - Ok(()) - } + // fn q_set_songs(&self, index: usize, songs: Vec>) -> Result<(), Box> { + // let mail = &self.queue_mail[index]; + // mail.send(QueueCmd::SetSongs(songs))?; + // dbg!(mail.recv()?); + // Ok(()) + // } fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box> { let mail = &self.queue_mail[index]; diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 7eaace7..7785d4d 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -1,25 +1,104 @@ -use crate::{music_player::Player, music_storage::library::Song}; -use std::error::Error; +use uuid::Uuid; -pub struct Queue { +use crate::{music_player::Player, music_storage::library::{Album, Song}}; +use std::{error::Error, path::Path}; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum QueueState { + Played, + Current, + AddHere, + None, +} +#[derive(Debug, Clone)] +pub enum QueueItemType<'a> { + Song(Uuid), + Album(Album<'a>) +} +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct QueueItem<'a> { + item: QueueItemType<'a>, + state: QueueState +} + + +#[derive(Debug)] +pub struct Queue<'a> { pub player: Player, pub name: String, - pub songs: Vec, + pub items: Vec>, } -impl Queue { + +impl<'a> Queue<'a> { pub fn new() -> Result> { Ok( Queue { player: Player::new()?, name: String::new(), - songs: Vec::new() + items: Vec::new() } ) } - pub fn set_tracks(&mut self, tracks: Vec) { + pub fn set_items(&mut self, tracks: Vec>) { let mut tracks = tracks; - self.songs.clear(); - self.songs.append(&mut tracks); + self.items.clear(); + self.items.append(&mut tracks); + } + + pub fn current_index(&self) -> i16 { + let e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); + dbg!(&e); + e as i16 - 1 + } + + pub fn add_item(&mut self, item: QueueItemType<'a>) -> Result<(), Box> { + use QueueState::*; + let ind = self.current_index(); + let mut i: i16 = 1; + self.items = self.items.iter().enumerate().map(|(j, item_)| { + let mut item_ = item_.to_owned(); + if item_.state == AddHere { + i = j as i16 + 2; + item_.state = None; + } + if item_.state == Current { + i = j as i16 + 2; + } + item_ + }).collect::>(); + let pos = (ind + i) as usize; + dbg!(&pos); + self.items.insert( + pos, + QueueItem { + item: item.clone(), + state: if pos == self.items.len() && i == 1 { + Current + }else { + AddHere + } + } + ); + Ok(()) } } + + +#[test] +fn itemaddtest() { + let mut q = Queue::new().unwrap(); + q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); + dbg!(&q.items); + q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); + dbg!(&q.items); + q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); + dbg!(&q.items); + q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); + dbg!(&q.items); + q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); + dbg!(&q.items); + q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); + dbg!(&q.items); +} \ No newline at end of file diff --git a/src/music_player.rs b/src/music_player.rs index 12af300..9893088 100644 --- a/src/music_player.rs +++ b/src/music_player.rs @@ -90,6 +90,7 @@ enum PlaybackStats { } /// An instance of a music player with a GStreamer backend +#[derive(Debug)] pub struct Player { source: Option, //pub message_tx: Sender, From e175fe733778d67d0aa01c1d6bafef0c7e24ee8a Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Tue, 27 Feb 2024 01:23:09 -0500 Subject: [PATCH 11/28] added `remove_item()` function to Queue --- src/music_controller/queue.rs | 96 +++++++++++++++++++++++++---------- src/music_storage/playlist.rs | 2 + 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 7785d4d..05bb35e 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -1,7 +1,7 @@ use uuid::Uuid; -use crate::{music_player::Player, music_storage::library::{Album, Song}}; -use std::{error::Error, path::Path}; +use crate::{music_player::Player, music_storage::library::{Album, Song, URI}}; +use std::{error::Error, ops::Add, path::Path}; #[derive(Debug, PartialEq, Clone, Copy)] pub enum QueueState { @@ -11,15 +11,31 @@ pub enum QueueState { None, } #[derive(Debug, Clone)] +#[non_exhaustive] pub enum QueueItemType<'a> { Song(Uuid), - Album(Album<'a>) + ExternalSong(URI), + Album{ + album: Album<'a>, + shuffled: bool, + } +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum QueueSource { + Library, + Playlist(Uuid), + Search, + Queue, + File, } #[derive(Debug, Clone)] #[non_exhaustive] pub struct QueueItem<'a> { item: QueueItemType<'a>, - state: QueueState + state: QueueState, + source: QueueSource } @@ -47,29 +63,34 @@ impl<'a> Queue<'a> { self.items.append(&mut tracks); } - pub fn current_index(&self) -> i16 { - let e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); - dbg!(&e); - e as i16 - 1 + pub fn current_index(&mut self) -> i16 { + let mut i = 1; + let mut e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); + while e >= 51 { + self.items.remove(0); + e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); + i+=1; + } + e as i16 - 1 } - pub fn add_item(&mut self, item: QueueItemType<'a>) -> Result<(), Box> { + pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource) -> Result<(), Box> { use QueueState::*; let ind = self.current_index(); let mut i: i16 = 1; - self.items = self.items.iter().enumerate().map(|(j, item_)| { + self.items = self.items.iter().enumerate().map(|(j, item_)| { let mut item_ = item_.to_owned(); if item_.state == AddHere { - i = j as i16 + 2; + i = j as i16 + 1 - ind; item_.state = None; } if item_.state == Current { - i = j as i16 + 2; + i = j as i16 + 1 - ind; } item_ }).collect::>(); let pos = (ind + i) as usize; - dbg!(&pos); + // dbg!(&pos, &i, &ind); self.items.insert( pos, QueueItem { @@ -78,27 +99,48 @@ impl<'a> Queue<'a> { Current }else { AddHere - } + }, + source } ); Ok(()) } + + pub fn remove_item(&mut self, index: usize) -> Result<(), Box> { + use QueueState::*; + let ind = (self.current_index() + index as i16 + 1) as usize; + + if ind < self.items.len() { + // update the state of the next item to replace the item being removed + if self.items.get(ind + 1).is_some() { + self.items[ind + 1].state = self.items[ind].state; + } + self.items[ind].state = None; + self.items.remove(ind); + + Ok(()) + }else { + Err("No Songs to remove!".into()) + } + } } #[test] -fn itemaddtest() { +fn item_add_test() { let mut q = Queue::new().unwrap(); - q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); - dbg!(&q.items); - q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); - dbg!(&q.items); - q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); - dbg!(&q.items); - q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); - dbg!(&q.items); - q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); - dbg!(&q.items); - q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap(); - dbg!(&q.items); + for _ in 0..5 { + q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue }); + } + for _ in 0..3 { + q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library).unwrap(); + } + dbg!(&q.items, &q.items.len()); + + for _ in 0..1 { + q.remove_item(0).inspect_err(|e| println!("{e:?}")); + dbg!(&q.items.len()); + } + + dbg!(&q.items, &q.items.len()); } \ No newline at end of file diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs index 69ae390..ca1832b 100644 --- a/src/music_storage/playlist.rs +++ b/src/music_storage/playlist.rs @@ -24,6 +24,7 @@ pub enum SortOrder { } #[derive(Debug, Clone)] pub struct Playlist { + uuid: Uuid, title: String, cover: Option, tracks: Vec, @@ -152,6 +153,7 @@ impl Playlist { impl Default for Playlist { fn default() -> Self { Playlist { + uuid: Uuid::new_v4(), title: String::default(), cover: None, tracks: Vec::default(), From 1c14bc5ebbfa93dc448f63b57ca9e8558cdaaa52 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Tue, 27 Feb 2024 23:28:52 -0500 Subject: [PATCH 12/28] added queue clearing functions --- src/music_controller/queue.rs | 86 ++++++++++++++++++++++++++++------- src/music_storage/library.rs | 2 +- 2 files changed, 70 insertions(+), 18 deletions(-) diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 05bb35e..8764d8f 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -1,7 +1,8 @@ +use font::opentype::tables::font_variations::InstanceFlags; use uuid::Uuid; use crate::{music_player::Player, music_storage::library::{Album, Song, URI}}; -use std::{error::Error, ops::Add, path::Path}; +use std::{error::Error, path::Path}; #[derive(Debug, PartialEq, Clone, Copy)] pub enum QueueState { @@ -10,7 +11,7 @@ pub enum QueueState { AddHere, None, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub enum QueueItemType<'a> { Song(Uuid), @@ -18,10 +19,11 @@ pub enum QueueItemType<'a> { Album{ album: Album<'a>, shuffled: bool, - } + }, + None } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub enum QueueSource { Library, @@ -30,12 +32,23 @@ pub enum QueueSource { Queue, File, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub struct QueueItem<'a> { item: QueueItemType<'a>, state: QueueState, - source: QueueSource + source: QueueSource, + by_human: bool +} +impl QueueItem<'_> { + fn new() -> Self { + QueueItem { + item: QueueItemType::None, + state: QueueState::None, + source: QueueSource::Library, + by_human: false + } + } } @@ -66,7 +79,8 @@ impl<'a> Queue<'a> { pub fn current_index(&mut self) -> i16 { let mut i = 1; let mut e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); - while e >= 51 { + // TODO: make the max number of past songs modular + while e > 50 { self.items.remove(0); e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); i+=1; @@ -74,7 +88,7 @@ impl<'a> Queue<'a> { e as i16 - 1 } - pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource) -> Result<(), Box> { + pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource, by_human: bool) -> Result<(), Box> { use QueueState::*; let ind = self.current_index(); let mut i: i16 = 1; @@ -100,7 +114,8 @@ impl<'a> Queue<'a> { }else { AddHere }, - source + source, + by_human } ); Ok(()) @@ -114,15 +129,52 @@ impl<'a> Queue<'a> { // update the state of the next item to replace the item being removed if self.items.get(ind + 1).is_some() { self.items[ind + 1].state = self.items[ind].state; + }else if self.items[ind].state != Current { + self.items[ind - 1].state = self.items[ind].state; } self.items[ind].state = None; self.items.remove(ind); - Ok(()) }else { Err("No Songs to remove!".into()) } } + + pub fn clear(&mut self) { + self.items.retain(|item| item.state == QueueState::Played ); + } + + pub fn clear_except(&mut self, index: usize) -> Result<(), Box> { + let mut index = index; + let ind = self.current_index(); + + if ind != -1 { + index += ind as usize; + }else { + index -=1 + } + + if !self.is_empty() && index < self.items.len() { + let i = self.items[index].clone(); + self.items.retain(|item| item.state == QueueState::Played || *item == i ); + self.items[(ind+1) as usize].state = QueueState::Current + }else { + return Err("index out of bounds!".into()); + } + Ok(()) + } + + pub fn clear_played(&mut self) { + self.items.retain(|item| item.state != QueueState::Played ); + } + + pub fn clear_all(&mut self) { + self.items.clear() + } + + fn is_empty(&self) -> bool { + self.items.iter().filter(|item| item.state != QueueState::Played).collect::>().len() == 0 + } } @@ -130,17 +182,17 @@ impl<'a> Queue<'a> { fn item_add_test() { let mut q = Queue::new().unwrap(); for _ in 0..5 { - q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue }); + q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false }); } + q.clear(); + for _ in 0..5 { + q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); + } + // q.clear_played(); for _ in 0..3 { - q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library).unwrap(); - } - dbg!(&q.items, &q.items.len()); - - for _ in 0..1 { q.remove_item(0).inspect_err(|e| println!("{e:?}")); - dbg!(&q.items.len()); } + q.clear_except(4).inspect_err(|e| println!("{e:?}")); dbg!(&q.items, &q.items.len()); } \ No newline at end of file diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index 93b9991..9915682 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -515,7 +515,7 @@ pub enum Service { None, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Album<'a> { title: &'a String, artist: Option<&'a String>, From 588a9cbd94ef06e1f5a42d8881bcebb34f00ce8b Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sun, 10 Mar 2024 19:22:31 -0400 Subject: [PATCH 13/28] Added more queue functions --- src/music_controller/queue.rs | 192 ++++++++++++++++++++++++++++++---- src/music_player.rs | 2 +- 2 files changed, 175 insertions(+), 19 deletions(-) diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 8764d8f..f727fea 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -1,8 +1,8 @@ use font::opentype::tables::font_variations::InstanceFlags; use uuid::Uuid; -use crate::{music_player::Player, music_storage::library::{Album, Song, URI}}; -use std::{error::Error, path::Path}; +use crate::{music_player::Player, music_storage::library::{Album, MusicLibrary, Song, URI}}; +use std::{error::Error, ops::Add, path::Path, sync::{Arc, RwLock}, thread::sleep, time::Duration}; #[derive(Debug, PartialEq, Clone, Copy)] pub enum QueueState { @@ -19,8 +19,36 @@ pub enum QueueItemType<'a> { Album{ album: Album<'a>, shuffled: bool, + // disc #, track # + current: (i32, i32) }, - None + None, + Test +} +impl QueueItemType<'_> { + fn get_uri(&self, lib: Arc>) -> Option { + use QueueItemType::*; + + let lib = lib.read().unwrap(); + match self { + Song(uuid) => { + if let Some((song, _)) = lib.query_uuid(uuid) { + Some(song.location.clone()) + }else { + Option::None + } + }, + Album{album, shuffled, current: (disc, index)} => { + if !shuffled { + Some(album.track(*disc as usize, *index as usize).unwrap().location.clone()) + }else { + todo!() + } + }, + ExternalSong(uri) => { Some(uri.clone()) }, + _ => { Option::None } + } + } } #[derive(Debug, Clone, PartialEq)] @@ -70,12 +98,6 @@ impl<'a> Queue<'a> { ) } - pub fn set_items(&mut self, tracks: Vec>) { - let mut tracks = tracks; - self.items.clear(); - self.items.append(&mut tracks); - } - pub fn current_index(&mut self) -> i16 { let mut i = 1; let mut e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); @@ -88,12 +110,27 @@ impl<'a> Queue<'a> { e as i16 - 1 } + fn contains_state(&self, state: QueueState) -> bool { + !self.items.iter().filter(|item| item.state == state ).collect::>().is_empty() + } + + fn is_empty(&self) -> bool { + self.items.iter().filter(|item| item.state != QueueState::Played).collect::>().is_empty() + } + + pub fn set_items(&mut self, tracks: Vec>) { + let mut tracks = tracks; + self.items.clear(); + self.items.append(&mut tracks); + } + pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource, by_human: bool) -> Result<(), Box> { use QueueState::*; let ind = self.current_index(); let mut i: i16 = 1; - self.items = self.items.iter().enumerate().map(|(j, item_)| { + self.items = self.items.iter().enumerate().map(|(j, item_)| { let mut item_ = item_.to_owned(); + // get the index of the current AddHere item and give it to i if item_.state == AddHere { i = j as i16 + 1 - ind; item_.state = None; @@ -121,6 +158,33 @@ impl<'a> Queue<'a> { Ok(()) } + pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: QueueSource) { + use QueueState::*; + let ind = self.current_index(); + let empty = self.is_empty(); + + self.items.insert( + // index would go out of bounds if empty ( current index = -1 ) + if empty { + (ind + 1) as usize + }else { + (ind + 2) as usize + }, + QueueItem { + item, + state: if empty { + Current + }else if self.items.get((ind + 1) as usize).is_none() || (!self.contains_state(AddHere) && self.items.get((ind + 1) as usize).is_some()) { + AddHere + }else { + None + }, + source, + by_human: true + } + ) + } + pub fn remove_item(&mut self, index: usize) -> Result<(), Box> { use QueueState::*; let ind = (self.current_index() + index as i16 + 1) as usize; @@ -147,14 +211,15 @@ impl<'a> Queue<'a> { pub fn clear_except(&mut self, index: usize) -> Result<(), Box> { let mut index = index; let ind = self.current_index(); + let empty = self.is_empty(); - if ind != -1 { + if !empty { index += ind as usize; }else { index -=1 } - if !self.is_empty() && index < self.items.len() { + if !empty && index < self.items.len() { let i = self.items[index].clone(); self.items.retain(|item| item.state == QueueState::Played || *item == i ); self.items[(ind+1) as usize].state = QueueState::Current @@ -172,8 +237,65 @@ impl<'a> Queue<'a> { self.items.clear() } - fn is_empty(&self) -> bool { - self.items.iter().filter(|item| item.state != QueueState::Played).collect::>().len() == 0 + fn move_to(&mut self, index: usize) -> Result<(), Box> { + let mut index = index; + let empty = self.is_empty(); + let ind = self.current_index(); + + if !empty { + index += ind as usize; + }else { + return Err("Nothing in the queue to move to!".into()); + } + + dbg!(1); + if !empty && index < self.items.len() -1 { + // TODO: make this check for player position + let pos = self.player.position(); + if pos.is_some_and(|dur| !dur.is_zero() ) { + self.items[ind as usize].state = QueueState::Played + } + dbg!(2); + + let to_item = self.items[index].clone(); + let new_ind = self.current_index() as usize; + dbg!(3); + + // dbg!(&self.items, &new_ind, &to_item.item, &self.items[new_ind + 1].item, &self.items.len()); + loop { + dbg!(4); + + if self.items[new_ind + 1].item != to_item.item { + self.remove_item(0); + dbg!(&self.items, &new_ind, &to_item.item, &self.items[new_ind + 1].item, &self.items.len()); + sleep(Duration::from_millis(1000)); + }else { + break; + } + } + }else { + return Err("index out of bounds!".into()); + } + Ok(()) + } + + pub fn swap(&mut self, index1: usize, index2: usize) {} + + pub fn move_item(&mut self, item: usize, to_index: usize) {} + + pub fn next() {} + + pub fn prev() {} + + pub fn enqueue_item(&mut self, item: QueueItem, lib: Arc>) -> Result<(), Box> { + use QueueItemType::*; + + if let Some(uri) = item.item.get_uri(lib) { + self.player.enqueue_next(&uri)?; + }else { + return Err("this item does not exist!".into()); + } + Ok(()) } } @@ -185,14 +307,48 @@ fn item_add_test() { q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false }); } q.clear(); - for _ in 0..5 { + for _ in 0..1 { q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); } // q.clear_played(); - for _ in 0..3 { - q.remove_item(0).inspect_err(|e| println!("{e:?}")); + // for _ in 0..3 { + // q.remove_item(0).inspect_err(|e| println!("{e:?}")); + // } + for _ in 0..2 { + q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::None, source: QueueSource::Queue, by_human: false }); } - q.clear_except(4).inspect_err(|e| println!("{e:?}")); + q.add_item_next(QueueItemType::Test, QueueSource::File); + dbg!(&q.items, &q.items.len()); +} + +#[test] +fn test_() { + let mut q = Queue::new().unwrap(); + for _ in 0..100 { + q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false }); + } + for _ in 0..2 { + q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); + } + q.add_item_next(QueueItemType::Test, QueueSource::Queue); + + dbg!(&q.items, &q.items.len()); + +} + +#[test] +fn move_test() { + let mut q = Queue::new().unwrap(); + // for _ in 0..1 { + // q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false }); + // } + for _ in 0..5 { + q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); + } + q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap(); + dbg!(&q.items, &q.items.len()); + + q.move_to(3).inspect_err(|e| {dbg!(e);}); dbg!(&q.items, &q.items.len()); } \ No newline at end of file diff --git a/src/music_player.rs b/src/music_player.rs index 9893088..e310403 100644 --- a/src/music_player.rs +++ b/src/music_player.rs @@ -180,7 +180,7 @@ impl Player { *position_update.write().unwrap() = None; break }, - PlaybackStats::Idle | PlaybackStats::Switching => println!("waiting!"), + PlaybackStats::Idle | PlaybackStats::Switching => {}, _ => () } From 96cfbe9a50fb3dcdfaceff8184a1ecf47788438f Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Mon, 11 Mar 2024 20:29:28 -0400 Subject: [PATCH 14/28] test commit --- src/music_controller/queue.rs | 164 ++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 78 deletions(-) diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index f727fea..516d04b 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -9,7 +9,7 @@ pub enum QueueState { Played, Current, AddHere, - None, + NoState, } #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] @@ -56,8 +56,6 @@ impl QueueItemType<'_> { pub enum QueueSource { Library, Playlist(Uuid), - Search, - Queue, File, } #[derive(Debug, Clone, PartialEq)] @@ -72,7 +70,7 @@ impl QueueItem<'_> { fn new() -> Self { QueueItem { item: QueueItemType::None, - state: QueueState::None, + state: QueueState::NoState, source: QueueSource::Library, by_human: false } @@ -88,6 +86,9 @@ pub struct Queue<'a> { } impl<'a> Queue<'a> { + fn dbg_items(&self) { + dbg!(self.items.iter().map(|item| item.item.clone() ).collect::>(), self.items.len()); + } pub fn new() -> Result> { Ok( Queue { @@ -98,16 +99,18 @@ impl<'a> Queue<'a> { ) } - pub fn current_index(&mut self) -> i16 { - let mut i = 1; + pub fn current_index(&mut self/* , max: usize */) -> Option { let mut e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); // TODO: make the max number of past songs modular while e > 50 { self.items.remove(0); - e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); - i+=1; + e -=1; + } + if e == 0 { + None + }else { + Some(e - 1) } - e as i16 - 1 } fn contains_state(&self, state: QueueState) -> bool { @@ -126,27 +129,28 @@ impl<'a> Queue<'a> { pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource, by_human: bool) -> Result<(), Box> { use QueueState::*; - let ind = self.current_index(); - let mut i: i16 = 1; + let mut i: usize = 0; + let ind = self.current_index(); + self.items = self.items.iter().enumerate().map(|(j, item_)| { let mut item_ = item_.to_owned(); // get the index of the current AddHere item and give it to i if item_.state == AddHere { - i = j as i16 + 1 - ind; - item_.state = None; - } - if item_.state == Current { - i = j as i16 + 1 - ind; + i = j - ind.unwrap_or(0); + item_.state = NoState; + } else if item_.state == Current { + i = j - ind.unwrap_or(0); } item_ }).collect::>(); - let pos = (ind + i) as usize; + + let pos = ind.unwrap_or(0) + i + if !self.is_empty() || (self.is_empty() && ind == None) { 0 } else { 1 }; // dbg!(&pos, &i, &ind); self.items.insert( pos, QueueItem { item: item.clone(), - state: if pos == self.items.len() && i == 1 { + state: if pos == self.items.len() && i == 0 { Current }else { AddHere @@ -160,24 +164,20 @@ impl<'a> Queue<'a> { pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: QueueSource) { use QueueState::*; - let ind = self.current_index(); + let ind_ = self.current_index(); + let ind = ind_.unwrap_or(0); let empty = self.is_empty(); self.items.insert( - // index would go out of bounds if empty ( current index = -1 ) - if empty { - (ind + 1) as usize - }else { - (ind + 2) as usize - }, + (ind + if !empty && ind_ == None { 1 } else { 2 }), QueueItem { item, state: if empty { Current - }else if self.items.get((ind + 1) as usize).is_none() || (!self.contains_state(AddHere) && self.items.get((ind + 1) as usize).is_some()) { + }else if self.items.get(ind + 1).is_none() || (!self.contains_state(AddHere) && self.items.get(ind + 1).is_some()) { AddHere }else { - None + NoState }, source, by_human: true @@ -187,17 +187,19 @@ impl<'a> Queue<'a> { pub fn remove_item(&mut self, index: usize) -> Result<(), Box> { use QueueState::*; - let ind = (self.current_index() + index as i16 + 1) as usize; + let remove_index: usize = (if let Some(current_index) = self.current_index() { dbg!(¤t_index); current_index } else { 0 } + index ); - if ind < self.items.len() { + // dbg!(/*&remove_index, self.current_index(), &index,*/ &self.items[remove_index]); + + if remove_index < self.items.len() { // update the state of the next item to replace the item being removed - if self.items.get(ind + 1).is_some() { - self.items[ind + 1].state = self.items[ind].state; - }else if self.items[ind].state != Current { - self.items[ind - 1].state = self.items[ind].state; + if self.items.get(remove_index + 1).is_some() { + self.items[remove_index + 1].state = self.items[remove_index].state; + }else if self.items[remove_index].state != Current { + self.items[remove_index - 1].state = self.items[remove_index].state; } - self.items[ind].state = None; - self.items.remove(ind); + self.items[remove_index].state = NoState; + self.items.remove(remove_index); Ok(()) }else { Err("No Songs to remove!".into()) @@ -210,11 +212,14 @@ impl<'a> Queue<'a> { pub fn clear_except(&mut self, index: usize) -> Result<(), Box> { let mut index = index; - let ind = self.current_index(); + let ind = match self.current_index() { + Some(e) => e, + None => return Err("nothing to clear!".into()) + }; let empty = self.is_empty(); if !empty { - index += ind as usize; + index += ind; }else { index -=1 } @@ -222,7 +227,7 @@ impl<'a> Queue<'a> { if !empty && index < self.items.len() { let i = self.items[index].clone(); self.items.retain(|item| item.state == QueueState::Played || *item == i ); - self.items[(ind+1) as usize].state = QueueState::Current + self.items[ind+1].state = QueueState::Current }else { return Err("index out of bounds!".into()); } @@ -238,37 +243,26 @@ impl<'a> Queue<'a> { } fn move_to(&mut self, index: usize) -> Result<(), Box> { - let mut index = index; let empty = self.is_empty(); - let ind = self.current_index(); + let nothing_error = Err("Nothing in the queue to move to!".into()); + let ind = self.current_index().unwrap_or(0); + let index = if !empty { index + ind } else { return nothing_error; }; - if !empty { - index += ind as usize; - }else { - return Err("Nothing in the queue to move to!".into()); - } - - dbg!(1); if !empty && index < self.items.len() -1 { - // TODO: make this check for player position - let pos = self.player.position(); - if pos.is_some_and(|dur| !dur.is_zero() ) { - self.items[ind as usize].state = QueueState::Played + let position = self.player.position(); + if position.is_some_and(|dur| !dur.is_zero() ) { + self.items[ind].state = QueueState::Played; } - dbg!(2); let to_item = self.items[index].clone(); - let new_ind = self.current_index() as usize; - dbg!(3); + let ind = self.current_index().unwrap_or(0); - // dbg!(&self.items, &new_ind, &to_item.item, &self.items[new_ind + 1].item, &self.items.len()); loop { - dbg!(4); - - if self.items[new_ind + 1].item != to_item.item { - self.remove_item(0); - dbg!(&self.items, &new_ind, &to_item.item, &self.items[new_ind + 1].item, &self.items.len()); - sleep(Duration::from_millis(1000)); + if self.items[ind].item != to_item.item { + if let Err(e) = self.remove_item(0) { + dbg!(&e); self.dbg_items(); return Err(e); + } + // dbg!(&to_item.item, &self.items[ind].item); }else { break; } @@ -303,21 +297,34 @@ impl<'a> Queue<'a> { #[test] fn item_add_test() { let mut q = Queue::new().unwrap(); - for _ in 0..5 { - q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false }); - } - q.clear(); + dbg!(1); for _ in 0..1 { + q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false }); + } + dbg!(2); + + // q.clear(); + dbg!(3); + + for _ in 0..5 { + // dbg!("tick!"); q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); + // dbg!(&q.items, &q.items.len()); } + dbg!(4); + dbg!(&q.items, &q.items.len()); + // q.clear_played(); - // for _ in 0..3 { - // q.remove_item(0).inspect_err(|e| println!("{e:?}")); - // } - for _ in 0..2 { - q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::None, source: QueueSource::Queue, by_human: false }); + for _ in 0..1 { + q.remove_item(0).inspect_err(|e| println!("{e:?}")); } - q.add_item_next(QueueItemType::Test, QueueSource::File); + // for _ in 0..2 { + // q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: QueueSource::Library, by_human: false }); + // } + // dbg!(5); + + // q.add_item_next(QueueItemType::Test, QueueSource::File); + // dbg!(6); dbg!(&q.items, &q.items.len()); } @@ -325,13 +332,13 @@ fn item_add_test() { #[test] fn test_() { let mut q = Queue::new().unwrap(); - for _ in 0..100 { - q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false }); + for _ in 0..1 { + q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false }); } for _ in 0..2 { q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); } - q.add_item_next(QueueItemType::Test, QueueSource::Queue); + q.add_item_next(QueueItemType::Test, QueueSource::File); dbg!(&q.items, &q.items.len()); @@ -340,15 +347,16 @@ fn test_() { #[test] fn move_test() { let mut q = Queue::new().unwrap(); - // for _ in 0..1 { - // q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false }); - // } + for _ in 0..1 { + q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false }); + } for _ in 0..5 { q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); } - q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap(); + // q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap(); dbg!(&q.items, &q.items.len()); q.move_to(3).inspect_err(|e| {dbg!(e);}); dbg!(&q.items, &q.items.len()); + // q.dbg_items(); } \ No newline at end of file From 7dcca941749c739cd9674cd6b2bfe2a0c54da7e5 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Fri, 15 Mar 2024 21:23:23 -0400 Subject: [PATCH 15/28] committing possibly broken code as a backup --- src/music_controller/queue.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 516d04b..fec5209 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -332,10 +332,10 @@ fn item_add_test() { #[test] fn test_() { let mut q = Queue::new().unwrap(); - for _ in 0..1 { + for _ in 0..400 { q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false }); } - for _ in 0..2 { + for _ in 0..50000 { q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); } q.add_item_next(QueueItemType::Test, QueueSource::File); From c4c842e637e03da7d8bb2a3827433bd7cc3cca3d Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sat, 23 Mar 2024 23:40:10 -0400 Subject: [PATCH 16/28] added function to show album art anf other small changes --- Cargo.toml | 3 ++ src/music_storage/library.rs | 85 ++++++++++++++++++++++++++++++++++-- src/music_storage/utils.rs | 18 +++++--- 3 files changed, 95 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0267611..a3c737b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,6 @@ font = "0.27.0" uuid = { version = "1.6.1", features = ["v4", "serde"]} serde_json = "1.0.111" deunicode = "1.4.2" +opener = { version = "0.7.0", features = ["reveal"]} +image = "0.25.0" +tempfile = "3.10.1" diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index 9915682..f91b590 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -5,16 +5,19 @@ use crate::config::config::Config; // Various std things use std::collections::BTreeMap; use std::error::Error; +use std::io::BufWriter; use std::ops::ControlFlow::{Break, Continue}; use std::ops::Deref; // Files use file_format::{FileFormat, Kind}; use glib::filename_to_uri; +use image::guess_format; use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt}; use rcue::parser::parse_from_file; +use tempfile::tempfile; use uuid::Uuid; -use std::fs; +use std::fs::{self, OpenOptions}; use std::path::{Path, PathBuf}; use walkdir::WalkDir; @@ -158,6 +161,15 @@ pub struct Song { pub tags: BTreeMap, } +#[test] +fn get_art_test() { + use urlencoding::decode; + + let s = Song::from_file(Path::new("F:\\Music\\Mp3\\ななひら\\Colory Starry\\05 - Fly Away!.mp3")).unwrap(); + s.open_album_art(0).inspect_err(|e| println!("{e:?}")); + +} + impl Song { /// Get a tag's value /// @@ -298,7 +310,7 @@ impl Song { /// creates a `Vec` from a cue file - pub fn from_cue(cuesheet: &Path) -> Result<(Vec<(Self, PathBuf)>), Box> { + pub fn from_cue(cuesheet: &Path) -> Result, Box> { let mut tracks = Vec::new(); let cue_data = parse_from_file(&cuesheet.to_string_lossy(), false).unwrap(); @@ -422,6 +434,71 @@ impl Song { } Ok(tracks) } + + pub fn open_album_art(&self, index: usize) -> Result<(), Box> { + use opener::open; + use urlencoding::decode; + + if index >= self.album_art.len() { + return Err("index out of bounds?".into()); + } + + let uri: String = match &self.album_art[index] { + AlbumArt::External(uri) => { + decode(match uri.as_uri().strip_prefix("file:///") { Some(e) => e, None => return Err("Invalid path?".into()) })?.into_owned() + }, + AlbumArt::Embedded(_) => { + + let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed); + let blank_tag = &lofty::Tag::new(TagType::Id3v2); + let tagged_file: lofty::TaggedFile; + + let uri = dbg!(urlencoding::decode(self.location.as_uri().strip_prefix("file:///").unwrap())?.into_owned()); + + let tag = match Probe::open(uri)?.options(normal_options).read() { + Ok(file) => { + tagged_file = file; + + match tagged_file.primary_tag() { + Some(primary_tag) => primary_tag, + + None => match tagged_file.first_tag() { + Some(first_tag) => first_tag, + None => blank_tag, + }, + } + } + + Err(_) => blank_tag, + }; + + let data = tag.pictures()[index].data(); + let format = dbg!(guess_format(data)?); + let img = image::load_from_memory(data)?; + + let mut location = String::new(); + let i: u32 = 0; + loop { + use image::ImageFormat::*; + //TODO: create a place for temporary images + let fmt = match format { + Jpeg => "jpeg", + Png => "png", + _ => todo!(), + }; + + location = format!("./test-config/images/tempcover{i}.{fmt}.tmp"); + break; + }; + img.save_with_format(&location, format)?; + + location.to_string() + }, + }; + open(uri)?; + + Ok(()) + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -526,7 +603,7 @@ pub struct Album<'a> { #[allow(clippy::len_without_is_empty)] impl Album<'_> { //returns the Album title - fn title(&self) -> &String { + pub fn title(&self) -> &String { self.title } @@ -1060,4 +1137,4 @@ impl MusicLibrary { Ok(albums) } -} +} \ No newline at end of file diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs index 0d6fb47..9d832e1 100644 --- a/src/music_storage/utils.rs +++ b/src/music_storage/utils.rs @@ -1,5 +1,7 @@ +use std::any::Any; use std::fs::{File, self}; use std::io::{BufReader, BufWriter}; +use std::os::windows::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::error::Error; @@ -74,20 +76,22 @@ pub fn find_images(song_path: &Path) -> Result, Box> { .follow_links(true) .into_iter() .filter_map(|e| e.ok()) + .filter(|e| e.depth() < 3) // Don't recurse very deep { - if target_file.depth() >= 3 { - // Don't recurse very deep - break; - } - + // println!("{:?}", target_file); let path = target_file.path(); - if !path.is_file() { + if !path.is_file() || !path.exists() { continue; } let format = FileFormat::from_file(path)?.kind(); if format != Kind::Image { - break; + continue; + } + + #[cfg(target_family = "windows")] + if (4 & path.metadata().unwrap().file_attributes()) == 4 { + continue; } let image_uri = URI::Local(path.to_path_buf().canonicalize()?); From 9da9b00befa600d6694e5b7ec7989580848f9008 Mon Sep 17 00:00:00 2001 From: G2-Games Date: Sat, 23 Mar 2024 23:34:47 -0500 Subject: [PATCH 17/28] Removed unused imports, moved some tests to `mod tests` --- src/config/config.rs | 93 +++++++++++--------- src/config/other_settings.rs | 4 - src/lib.rs | 2 +- src/music_controller/connections.rs | 2 - src/music_controller/controller.rs | 83 +++++++++-------- src/music_controller/queue.rs | 18 ++-- src/music_storage/db_reader/itunes/reader.rs | 35 +++++--- src/music_storage/library.rs | 56 ++++++------ src/music_storage/playlist.rs | 17 +--- src/music_storage/utils.rs | 6 +- 10 files changed, 152 insertions(+), 164 deletions(-) diff --git a/src/config/config.rs b/src/config/config.rs index 918af12..76c59cc 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -1,7 +1,7 @@ use std::{ path::PathBuf, fs::{File, OpenOptions, self}, - io::{Error, Write, Read}, sync::{Arc, RwLock}, + io::{Error, Write, Read}, }; use serde::{Deserialize, Serialize}; @@ -9,8 +9,6 @@ use serde_json::to_string_pretty; use thiserror::Error; use uuid::Uuid; -use crate::music_storage::library::{MusicLibrary, self}; - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConfigLibrary { pub name: String, @@ -166,47 +164,54 @@ pub enum ConfigError { } -#[test] -fn config_test() { - let lib_a = ConfigLibrary::new(PathBuf::from("test-config/library1"), String::from("library1"), None); - let lib_b = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None); - let lib_c = ConfigLibrary::new(PathBuf::from("test-config/library3"), String::from("library3"), None); - let config = Config { - path: PathBuf::from("test-config/config_test.json"), - libraries: ConfigLibraries { - libraries: vec![ - lib_a.clone(), - lib_b.clone(), - lib_c.clone(), - ], +#[cfg(test)] +mod tests { + use std::{path::PathBuf, sync::{Arc, RwLock}}; + use crate::music_storage::library::MusicLibrary; + use super::{Config, ConfigLibraries, ConfigLibrary}; + + #[test] + fn config_test() { + let lib_a = ConfigLibrary::new(PathBuf::from("test-config/library1"), String::from("library1"), None); + let lib_b = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None); + let lib_c = ConfigLibrary::new(PathBuf::from("test-config/library3"), String::from("library3"), None); + let config = Config { + path: PathBuf::from("test-config/config_test.json"), + libraries: ConfigLibraries { + libraries: vec![ + lib_a.clone(), + lib_b.clone(), + lib_c.clone(), + ], + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - }; - config.write_file(); - let arc = Arc::new(RwLock::from(config)); - MusicLibrary::init(arc.clone(), lib_a.uuid).unwrap(); - MusicLibrary::init(arc.clone(), lib_b.uuid).unwrap(); - MusicLibrary::init(arc.clone(), lib_c.uuid).unwrap(); + }; + config.write_file(); + let arc = Arc::new(RwLock::from(config)); + MusicLibrary::init(arc.clone(), lib_a.uuid).unwrap(); + MusicLibrary::init(arc.clone(), lib_b.uuid).unwrap(); + MusicLibrary::init(arc.clone(), lib_c.uuid).unwrap(); + } + + #[test] + fn test2() { + let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); + let uuid = config.libraries.get_default().unwrap().uuid; + let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap(); + lib.scan_folder("test-config/music/").unwrap(); + lib.save(config.clone()).unwrap(); + dbg!(&lib); + dbg!(&config); + } + + #[test] + fn test3() { + let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); + let uuid = config.libraries.get_default().unwrap().uuid; + let lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap(); + + dbg!(lib); + } } - -#[test] -fn test2() { - let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); - let uuid = config.libraries.get_default().unwrap().uuid; - let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap(); - lib.scan_folder("test-config/music/").unwrap(); - lib.save(config.clone()).unwrap(); - dbg!(&lib); - dbg!(&config); -} - -#[test] -fn test3() { - let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); - let uuid = config.libraries.get_default().unwrap().uuid; - let lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap(); - - dbg!(lib); -} \ No newline at end of file diff --git a/src/config/other_settings.rs b/src/config/other_settings.rs index da94670..164221f 100644 --- a/src/config/other_settings.rs +++ b/src/config/other_settings.rs @@ -1,7 +1,3 @@ -use std::{marker::PhantomData, fs::File, path::PathBuf}; - -use font::Font; - pub enum Setting { String { name: String, diff --git a/src/lib.rs b/src/lib.rs index a9d0db0..379a56c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![allow(unused)] +#[allow(dead_code)] pub mod music_storage { pub mod library; diff --git a/src/music_controller/connections.rs b/src/music_controller/connections.rs index 86d8f29..ac4abd9 100644 --- a/src/music_controller/connections.rs +++ b/src/music_controller/connections.rs @@ -1,5 +1,3 @@ -use std::{env, thread, time}; - use super::controller::Controller; diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index 8d7f08e..ebcc27f 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -2,33 +2,23 @@ //! player. It manages queues, playback, library access, and //! other functions -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::{Arc, RwLock}; -use std::time::Duration; use crossbeam_channel::{Sender, Receiver}; -// use std::sync::mpsc; use crossbeam_channel; -use gstreamer::format::Default; -use gstreamer::query::Uri; -use std::thread::{self, sleep, spawn}; +use std::thread::spawn; use std::error::Error; use crossbeam_channel::unbounded; -use rayon::iter::Rev; use uuid::Uuid; -use crate::config; use crate::music_storage::library::{Tag, URI}; -use crate::music_storage::playlist::Playlist; use crate::{ - music_player::Player, music_storage::library::{MusicLibrary, Song}, config::config::Config, music_controller::queue::Queue, }; -use super::queue::{QueueItem, QueueState}; - pub struct Controller { // queues: Vec, config: Arc>, @@ -266,7 +256,7 @@ impl Controller { } }); self.queue_mail.push(out_thread_queue); - Ok((self.queue_mail.len() - 1)) + Ok(self.queue_mail.len() - 1) } fn q_play(&self, index: usize) -> Result<(), Box> { @@ -306,36 +296,43 @@ impl Controller { } -#[test] -fn play_test() { - let mut a = match Controller::start("test-config/config_test.json".to_string()) { - Ok(c) => c, - Err(e) => panic!("{e}") - }; - sleep(Duration::from_millis(500)); +#[cfg(test)] +mod tests { + use std::{thread::sleep, time::Duration}; - let i = a.q_new().unwrap(); - a.q_set_volume(i, 0.04); - // a.new_queue(); - let songs = a.lib_get_songs(); - a.q_enqueue(i, songs[2].location.clone()); - // a.enqueue(1, songs[2].location.clone()); - a.q_play(i).unwrap(); - // a.play(1).unwrap(); + use super::Controller; - sleep(Duration::from_secs(10)); - a.q_pause(i); - sleep(Duration::from_secs(10)); - a.q_play(i); - sleep(Duration::from_secs(1000)); -} - -#[test] -fn test_() { - let a = match Controller::start("test-config/config_test.json".to_string()) { - Ok(c) => c, - Err(e) => panic!("{e}") - }; - a.lib_scan_folder("F:/Music/Mp3".to_string()); - a.lib_save(); + #[test] + fn play_test() { + let mut a = match Controller::start("test-config/config_test.json".to_string()) { + Ok(c) => c, + Err(e) => panic!("{e}") + }; + sleep(Duration::from_millis(500)); + + let i = a.q_new().unwrap(); + a.q_set_volume(i, 0.04); + // a.new_queue(); + let songs = a.lib_get_songs(); + a.q_enqueue(i, songs[2].location.clone()); + // a.enqueue(1, songs[2].location.clone()); + a.q_play(i).unwrap(); + // a.play(1).unwrap(); + + sleep(Duration::from_secs(10)); + a.q_pause(i); + sleep(Duration::from_secs(10)); + a.q_play(i); + sleep(Duration::from_secs(1000)); + } + + #[test] + fn test_() { + let a = match Controller::start("test-config/config_test.json".to_string()) { + Ok(c) => c, + Err(e) => panic!("{e}") + }; + a.lib_scan_folder("F:/Music/Mp3".to_string()); + a.lib_save(); + } } diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index fec5209..8dfae31 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -1,8 +1,12 @@ -use font::opentype::tables::font_variations::InstanceFlags; use uuid::Uuid; - -use crate::{music_player::Player, music_storage::library::{Album, MusicLibrary, Song, URI}}; -use std::{error::Error, ops::Add, path::Path, sync::{Arc, RwLock}, thread::sleep, time::Duration}; +use crate::{ + music_player::Player, + music_storage::library::{Album, MusicLibrary, URI} +}; +use std::{ + error::Error, + sync::{Arc, RwLock} +}; #[derive(Debug, PartialEq, Clone, Copy)] pub enum QueueState { @@ -169,7 +173,7 @@ impl<'a> Queue<'a> { let empty = self.is_empty(); self.items.insert( - (ind + if !empty && ind_ == None { 1 } else { 2 }), + ind + if !empty && ind_ == None { 1 } else { 2 }, QueueItem { item, state: if empty { @@ -282,8 +286,6 @@ impl<'a> Queue<'a> { pub fn prev() {} pub fn enqueue_item(&mut self, item: QueueItem, lib: Arc>) -> Result<(), Box> { - use QueueItemType::*; - if let Some(uri) = item.item.get_uri(lib) { self.player.enqueue_next(&uri)?; }else { @@ -359,4 +361,4 @@ fn move_test() { q.move_to(3).inspect_err(|e| {dbg!(e);}); dbg!(&q.items, &q.items.len()); // q.dbg_items(); -} \ No newline at end of file +} diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs index decf390..90a48f2 100644 --- a/src/music_storage/db_reader/itunes/reader.rs +++ b/src/music_storage/db_reader/itunes/reader.rs @@ -7,15 +7,13 @@ use std::collections::{BTreeMap, HashMap}; use std::fs::File; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::sync::{Arc, RwLock}; use std::time::Duration as StdDur; use std::vec::Vec; use chrono::prelude::*; -use crate::config::config::{Config, ConfigLibrary}; use crate::music_storage::db_reader::extern_library::ExternalLibrary; -use crate::music_storage::library::{AlbumArt, MusicLibrary, Service, Song, Tag, URI, BannedType}; +use crate::music_storage::library::{AlbumArt, Service, Song, Tag, URI}; use crate::music_storage::utils; use urlencoding::decode; @@ -331,18 +329,27 @@ impl ITunesSong { } } -#[test] -fn itunes_lib_test() { - let mut config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); - let config_lib = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None); - config.libraries.libraries.push(config_lib.clone()); +#[cfg(test)] +mod tests { + use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}}; - let songs = ITunesLibrary::from_file(Path::new("test-config\\iTunesLib.xml")).to_songs(); + use crate::{config::config::{Config, ConfigLibrary}, music_storage::{db_reader::extern_library::ExternalLibrary, library::MusicLibrary}}; - let mut library = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), config_lib.uuid).unwrap(); + use super::ITunesLibrary; - songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap()); + #[test] + fn itunes_lib_test() { + let mut config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); + let config_lib = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None); + config.libraries.libraries.push(config_lib.clone()); - config.write_file().unwrap(); - library.save(config).unwrap(); -} \ No newline at end of file + let songs = ITunesLibrary::from_file(Path::new("test-config\\iTunesLib.xml")).to_songs(); + + let mut library = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), config_lib.uuid).unwrap(); + + songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap()); + + config.write_file().unwrap(); + library.save(config).unwrap(); + } +} diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index f91b590..8c547c0 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -5,9 +5,7 @@ use crate::config::config::Config; // Various std things use std::collections::BTreeMap; use std::error::Error; -use std::io::BufWriter; use std::ops::ControlFlow::{Break, Continue}; -use std::ops::Deref; // Files use file_format::{FileFormat, Kind}; @@ -15,11 +13,12 @@ use glib::filename_to_uri; use image::guess_format; use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt}; use rcue::parser::parse_from_file; -use tempfile::tempfile; use uuid::Uuid; -use std::fs::{self, OpenOptions}; +use std::fs; +use tempfile::TempDir; use std::path::{Path, PathBuf}; use walkdir::WalkDir; +use image::ImageFormat::*; // Time use chrono::{serde::ts_milliseconds_option, DateTime, Utc}; @@ -163,11 +162,8 @@ pub struct Song { #[test] fn get_art_test() { - use urlencoding::decode; - let s = Song::from_file(Path::new("F:\\Music\\Mp3\\ななひら\\Colory Starry\\05 - Fly Away!.mp3")).unwrap(); - s.open_album_art(0).inspect_err(|e| println!("{e:?}")); - + s.open_album_art(0).inspect_err(|e| println!("{e:?}")).unwrap(); } impl Song { @@ -440,22 +436,24 @@ impl Song { use urlencoding::decode; if index >= self.album_art.len() { - return Err("index out of bounds?".into()); + return Err("Index out of bounds".into()); } - let uri: String = match &self.album_art[index] { + let uri = match &self.album_art[index] { AlbumArt::External(uri) => { - decode(match uri.as_uri().strip_prefix("file:///") { Some(e) => e, None => return Err("Invalid path?".into()) })?.into_owned() + PathBuf::from(decode(match uri.as_uri().strip_prefix("file:///") { + Some(e) => e, + None => return Err("Invalid path?".into()) + })?.to_owned().to_string()) }, AlbumArt::Embedded(_) => { - let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed); let blank_tag = &lofty::Tag::new(TagType::Id3v2); let tagged_file: lofty::TaggedFile; - let uri = dbg!(urlencoding::decode(self.location.as_uri().strip_prefix("file:///").unwrap())?.into_owned()); + let uri = urlencoding::decode(self.location.as_uri().strip_prefix("file://").unwrap())?.into_owned(); - let tag = match Probe::open(uri)?.options(normal_options).read() { + let tag = match Probe::open(uri).unwrap().options(normal_options).read() { Ok(file) => { tagged_file = file; @@ -476,26 +474,22 @@ impl Song { let format = dbg!(guess_format(data)?); let img = image::load_from_memory(data)?; - let mut location = String::new(); - let i: u32 = 0; - loop { - use image::ImageFormat::*; - //TODO: create a place for temporary images - let fmt = match format { - Jpeg => "jpeg", - Png => "png", - _ => todo!(), - }; - - location = format!("./test-config/images/tempcover{i}.{fmt}.tmp"); - break; + let tmp_dir = TempDir::new()?; + let fmt = match format { + Jpeg => "jpeg", + Png => "png", + _ => todo!(), }; - img.save_with_format(&location, format)?; - location.to_string() + let file_path = tmp_dir.path().join(format!("{}.{fmt}", self.uuid)); + + open(&file_path).unwrap(); + img.save_with_format(&file_path, format).unwrap(); + + file_path }, }; - open(uri)?; + dbg!(open(uri)?); Ok(()) } @@ -1137,4 +1131,4 @@ impl MusicLibrary { Ok(albums) } -} \ No newline at end of file +} diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs index ca1832b..783a293 100644 --- a/src/music_storage/playlist.rs +++ b/src/music_storage/playlist.rs @@ -1,21 +1,10 @@ -use std::{fs::File, path::{Path, PathBuf}, io::{Read, Error}}; +use std::{fs::File, io::{Read, Error}}; -use bincode::config; use chrono::Duration; use uuid::Uuid; -// use walkdir::Error; +use super::library::{AlbumArt, Song, Tag}; -use crate::music_controller::controller::Controller; - -use super::{ - library::{AlbumArt, Song, Tag}, - music_collection::MusicCollection, db_reader::{ - itunes::reader::ITunesLibrary, - extern_library::ExternalLibrary - }, -}; - -use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2, MasterPlaylist}; +use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2}; #[derive(Debug, Clone)] pub enum SortOrder { diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs index 9d832e1..1f68a8c 100644 --- a/src/music_storage/utils.rs +++ b/src/music_storage/utils.rs @@ -1,10 +1,7 @@ -use std::any::Any; use std::fs::{File, self}; use std::io::{BufReader, BufWriter}; -use std::os::windows::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::error::Error; - use walkdir::WalkDir; use file_format::{FileFormat, Kind}; use snap; @@ -12,6 +9,9 @@ use deunicode::deunicode_with_tofu; use super::library::{AlbumArt, URI}; +#[cfg(target_family = "windows")] +use std::os::windows::fs::MetadataExt; + pub(super) fn normalize(input_string: &str) -> String { // Normalize the string to latin characters... this needs a lot of work let mut normalized = deunicode_with_tofu(input_string, " "); From 162982ef868cda38161c223d9b754fb82033be58 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sun, 24 Mar 2024 21:36:22 -0400 Subject: [PATCH 18/28] Updated album art viewer function to --- src/lib.rs | 2 - src/music_controller/queue.rs | 312 ++++++++++++++++++++-------------- src/music_storage/library.rs | 75 +++++--- 3 files changed, 235 insertions(+), 154 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 379a56c..387a488 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#[allow(dead_code)] - pub mod music_storage { pub mod library; pub mod music_collection; diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 8dfae31..6180e9d 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -1,6 +1,6 @@ use uuid::Uuid; use crate::{ - music_player::Player, + music_player::{Player, PlayerError}, music_storage::library::{Album, MusicLibrary, URI} }; use std::{ @@ -8,13 +8,25 @@ use std::{ sync::{Arc, RwLock} }; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum QueueError { + #[error("Index out of bounds! Index {0} is over len {1}")] + OutOfBounds(usize, usize), + #[error("The Queue is empty!")] + EmptyQueue + +} + #[derive(Debug, PartialEq, Clone, Copy)] pub enum QueueState { Played, - Current, + First, AddHere, NoState, } + #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub enum QueueItemType<'a> { @@ -23,12 +35,20 @@ pub enum QueueItemType<'a> { Album{ album: Album<'a>, shuffled: bool, + order: Option>, // disc #, track # current: (i32, i32) }, + Playlist { + uuid: Uuid, + shuffled: bool, + order: Option>, + current: Uuid + }, None, Test } + impl QueueItemType<'_> { fn get_uri(&self, lib: Arc>) -> Option { use QueueItemType::*; @@ -42,7 +62,7 @@ impl QueueItemType<'_> { Option::None } }, - Album{album, shuffled, current: (disc, index)} => { + Album{album, shuffled, current: (disc, index), ..} => { if !shuffled { Some(album.track(*disc as usize, *index as usize).unwrap().location.clone()) }else { @@ -55,19 +75,23 @@ impl QueueItemType<'_> { } } +// TODO: move this to a different location to be used elsewhere #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] -pub enum QueueSource { +pub enum PlayerLocation { + Test, Library, Playlist(Uuid), File, + Custom } + #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub struct QueueItem<'a> { item: QueueItemType<'a>, state: QueueState, - source: QueueSource, + source: PlayerLocation, by_human: bool } impl QueueItem<'_> { @@ -75,7 +99,7 @@ impl QueueItem<'_> { QueueItem { item: QueueItemType::None, state: QueueState::NoState, - source: QueueSource::Library, + source: PlayerLocation::Library, by_human: false } } @@ -87,111 +111,83 @@ pub struct Queue<'a> { pub player: Player, pub name: String, pub items: Vec>, + pub played: Vec>, + pub loop_: bool } impl<'a> Queue<'a> { + fn has_addhere(&self) -> bool { + for item in &self.items { + if item.state == QueueState::AddHere { + return true + } + } + false + } + fn dbg_items(&self) { dbg!(self.items.iter().map(|item| item.item.clone() ).collect::>(), self.items.len()); } - pub fn new() -> Result> { + + pub fn new() -> Result { Ok( Queue { player: Player::new()?, name: String::new(), - items: Vec::new() + items: Vec::new(), + played: Vec::new(), + loop_: false, } ) } - pub fn current_index(&mut self/* , max: usize */) -> Option { - let mut e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); - // TODO: make the max number of past songs modular - while e > 50 { - self.items.remove(0); - e -=1; - } - if e == 0 { - None - }else { - Some(e - 1) - } - } - - fn contains_state(&self, state: QueueState) -> bool { - !self.items.iter().filter(|item| item.state == state ).collect::>().is_empty() - } - - fn is_empty(&self) -> bool { - self.items.iter().filter(|item| item.state != QueueState::Played).collect::>().is_empty() - } - pub fn set_items(&mut self, tracks: Vec>) { let mut tracks = tracks; self.items.clear(); self.items.append(&mut tracks); } - pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource, by_human: bool) -> Result<(), Box> { - use QueueState::*; + pub fn add_item(&mut self, item: QueueItemType<'a>, source: PlayerLocation, by_human: bool) { let mut i: usize = 0; - let ind = self.current_index(); self.items = self.items.iter().enumerate().map(|(j, item_)| { let mut item_ = item_.to_owned(); // get the index of the current AddHere item and give it to i - if item_.state == AddHere { - i = j - ind.unwrap_or(0); - item_.state = NoState; - } else if item_.state == Current { - i = j - ind.unwrap_or(0); + if item_.state == QueueState::AddHere { + i = j; + item_.state = QueueState::NoState; } item_ }).collect::>(); - let pos = ind.unwrap_or(0) + i + if !self.is_empty() || (self.is_empty() && ind == None) { 0 } else { 1 }; - // dbg!(&pos, &i, &ind); - self.items.insert( - pos, - QueueItem { - item: item.clone(), - state: if pos == self.items.len() && i == 0 { - Current - }else { - AddHere - }, - source, - by_human - } - ); - Ok(()) + self.items.insert(i + if self.items.is_empty() { 0 } else { 1 }, QueueItem { + item, + state: QueueState::AddHere, + source, + by_human + }); } - pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: QueueSource) { + pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: PlayerLocation) { use QueueState::*; - let ind_ = self.current_index(); - let ind = ind_.unwrap_or(0); - let empty = self.is_empty(); + let empty = self.items.is_empty(); self.items.insert( - ind + if !empty && ind_ == None { 1 } else { 2 }, + (if empty { 0 } else { 1 }), QueueItem { item, - state: if empty { - Current - }else if self.items.get(ind + 1).is_none() || (!self.contains_state(AddHere) && self.items.get(ind + 1).is_some()) { - AddHere - }else { - NoState - }, + state: if (self.items.get(1).is_none() || (!self.has_addhere() && self.items.get(1).is_some()) || empty) { AddHere } else { NoState }, source, by_human: true } ) } - pub fn remove_item(&mut self, index: usize) -> Result<(), Box> { - use QueueState::*; - let remove_index: usize = (if let Some(current_index) = self.current_index() { dbg!(¤t_index); current_index } else { 0 } + index ); + pub fn add_multi(&mut self, items: Vec, source: PlayerLocation, by_human: bool) { + + } + + pub fn remove_item(&mut self, remove_index: usize) -> Result<(), QueueError> { // dbg!(/*&remove_index, self.current_index(), &index,*/ &self.items[remove_index]); @@ -199,39 +195,29 @@ impl<'a> Queue<'a> { // update the state of the next item to replace the item being removed if self.items.get(remove_index + 1).is_some() { self.items[remove_index + 1].state = self.items[remove_index].state; - }else if self.items[remove_index].state != Current { - self.items[remove_index - 1].state = self.items[remove_index].state; } - self.items[remove_index].state = NoState; + self.items[remove_index].state = QueueState::NoState; self.items.remove(remove_index); Ok(()) }else { - Err("No Songs to remove!".into()) + Err(QueueError::EmptyQueue) } } pub fn clear(&mut self) { - self.items.retain(|item| item.state == QueueState::Played ); + self.items.clear(); } pub fn clear_except(&mut self, index: usize) -> Result<(), Box> { - let mut index = index; - let ind = match self.current_index() { - Some(e) => e, - None => return Err("nothing to clear!".into()) - }; - let empty = self.is_empty(); - - if !empty { - index += ind; - }else { - index -=1 - } + use QueueState::*; + let empty = self.items.is_empty(); if !empty && index < self.items.len() { let i = self.items[index].clone(); - self.items.retain(|item| item.state == QueueState::Played || *item == i ); - self.items[ind+1].state = QueueState::Current + self.items.retain(|item| *item == i ); + self.items[0].state = AddHere; + }else if empty { + return Err("Queue is empty!".into()); }else { return Err("index out of bounds!".into()); } @@ -239,49 +225,117 @@ impl<'a> Queue<'a> { } pub fn clear_played(&mut self) { - self.items.retain(|item| item.state != QueueState::Played ); + self.played.clear(); } pub fn clear_all(&mut self) { - self.items.clear() + self.items.clear(); + self.played.clear(); } - fn move_to(&mut self, index: usize) -> Result<(), Box> { - let empty = self.is_empty(); - let nothing_error = Err("Nothing in the queue to move to!".into()); - let ind = self.current_index().unwrap_or(0); - let index = if !empty { index + ind } else { return nothing_error; }; - if !empty && index < self.items.len() -1 { + // TODO: uh, fix this? + fn move_to(&mut self, index: usize) -> Result<(), QueueError> { + use QueueState::*; + + let empty = self.items.is_empty(); + let nothing_error = Err(QueueError::EmptyQueue); + let index = if !empty { index } else { return nothing_error; }; + + if !empty && index < self.items.len() { let position = self.player.position(); if position.is_some_and(|dur| !dur.is_zero() ) { - self.items[ind].state = QueueState::Played; + self.played.push(self.items[0].clone()); } let to_item = self.items[index].clone(); - let ind = self.current_index().unwrap_or(0); loop { - if self.items[ind].item != to_item.item { + let empty = !self.items.is_empty(); + let item = self.items[0].item.to_owned(); + + if item != to_item.item && !empty { + if self.items[0].state == AddHere && self.items.get(1).is_some() { + self.items[1].state = AddHere; + } if let Err(e) = self.remove_item(0) { dbg!(&e); self.dbg_items(); return Err(e); } // dbg!(&to_item.item, &self.items[ind].item); + }else if empty { + return nothing_error; }else { break; } } }else { - return Err("index out of bounds!".into()); + return Err(QueueError::EmptyQueue.into()); } Ok(()) } - pub fn swap(&mut self, index1: usize, index2: usize) {} + pub fn swap(&mut self, a: usize, b: usize) { + self.items.swap(a, b) + } - pub fn move_item(&mut self, item: usize, to_index: usize) {} + pub fn move_item(&mut self, a: usize, b: usize) { + let item = self.items[a].to_owned(); + if a != b { + self.items.remove(a); + } + self.items.insert(b, item); + } - pub fn next() {} + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self, lib: Arc>) -> Result> { + + + + if self.items.is_empty() { + if self.loop_ { + return Err(QueueError::EmptyQueue.into()); // TODO: add function to loop the queue + }else { + return Err(QueueError::EmptyQueue.into()); + } + } + // TODO: add an algorithm to detect if the song should be skipped + let item = self.items[0].clone(); + let uri: URI = match &self.items[1].item { + QueueItemType::Song(uuid) => { + // TODO: Refactor later for multiple URIs + match &lib.read().unwrap().query_uuid(uuid) { + Some(song) => song.0.location.clone(), + None => return Err("Uuid does not exist!".into()), + } + }, + QueueItemType::Album { album, current, ..} => { + let (disc, track) = (current.0 as usize, current.1 as usize); + match album.track(disc, track) { + Some(track) => track.location.clone(), + None => return Err(format!("Track in Album {} at disc {} track {} does not exist!", album.title(), disc, track).into()) + } + }, + QueueItemType::Playlist { current, .. } => { + // TODO: Refactor later for multiple URIs + match &lib.read().unwrap().query_uuid(current) { + Some(song) => song.0.location.clone(), + None => return Err("Uuid does not exist!".into()), + } + }, + _ => todo!() + }; + if !self.player.is_paused() { + self.player.enqueue_next(&uri)?; + self.player.play()? + } + if self.items[0].state == QueueState::AddHere || !self.has_addhere() { + self.items[1].state = QueueState::AddHere; + } + self.played.push(item); + self.items.remove(0); + + Ok(todo!()) + } pub fn prev() {} @@ -293,40 +347,42 @@ impl<'a> Queue<'a> { } Ok(()) } + pub fn check_played(&mut self) { + while self.played.len() > 50 { + self.played.remove(0); + } + } +} + +pub struct OutQueue { + +} + +pub enum OutQueueItem { + } #[test] fn item_add_test() { let mut q = Queue::new().unwrap(); - dbg!(1); - for _ in 0..1 { - q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false }); - } - dbg!(2); - - // q.clear(); - dbg!(3); for _ in 0..5 { // dbg!("tick!"); - q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); + q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true); // dbg!(&q.items, &q.items.len()); } - dbg!(4); - dbg!(&q.items, &q.items.len()); - // q.clear_played(); for _ in 0..1 { q.remove_item(0).inspect_err(|e| println!("{e:?}")); } - // for _ in 0..2 { - // q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: QueueSource::Library, by_human: false }); - // } - // dbg!(5); + for _ in 0..2 { + q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: PlayerLocation::Library, by_human: false }); + } + dbg!(5); - // q.add_item_next(QueueItemType::Test, QueueSource::File); - // dbg!(6); + q.add_item_next(QueueItemType::Test, PlayerLocation::Test); + dbg!(6); dbg!(&q.items, &q.items.len()); } @@ -335,25 +391,23 @@ fn item_add_test() { fn test_() { let mut q = Queue::new().unwrap(); for _ in 0..400 { - q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false }); + q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::NoState, source: PlayerLocation::File, by_human: false }); } for _ in 0..50000 { - q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); + q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true); } - q.add_item_next(QueueItemType::Test, QueueSource::File); + // q.add_item_next(QueueItemType::Test, PlayerLocation::File); - dbg!(&q.items, &q.items.len()); + // dbg!(&q.items, &q.items.len()); } #[test] fn move_test() { let mut q = Queue::new().unwrap(); - for _ in 0..1 { - q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false }); - } + for _ in 0..5 { - q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); + q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true); } // q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap(); dbg!(&q.items, &q.items.len()); diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index 8c547c0..1a198f8 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -5,7 +5,9 @@ use crate::config::config::Config; // Various std things use std::collections::BTreeMap; use std::error::Error; +use std::io::Write; use std::ops::ControlFlow::{Break, Continue}; +use std::thread::sleep; // Files use file_format::{FileFormat, Kind}; @@ -14,7 +16,7 @@ use image::guess_format; use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt}; use rcue::parser::parse_from_file; use uuid::Uuid; -use std::fs; +use std::fs::{self, File}; use tempfile::TempDir; use std::path::{Path, PathBuf}; use walkdir::WalkDir; @@ -160,11 +162,6 @@ pub struct Song { pub tags: BTreeMap, } -#[test] -fn get_art_test() { - let s = Song::from_file(Path::new("F:\\Music\\Mp3\\ななひら\\Colory Starry\\05 - Fly Away!.mp3")).unwrap(); - s.open_album_art(0).inspect_err(|e| println!("{e:?}")).unwrap(); -} impl Song { /// Get a tag's value @@ -431,7 +428,7 @@ impl Song { Ok(tracks) } - pub fn open_album_art(&self, index: usize) -> Result<(), Box> { + pub fn open_album_art(&self, index: usize, temp_dir: &TempDir) -> Result<(), Box> { use opener::open; use urlencoding::decode; @@ -451,9 +448,21 @@ impl Song { let blank_tag = &lofty::Tag::new(TagType::Id3v2); let tagged_file: lofty::TaggedFile; - let uri = urlencoding::decode(self.location.as_uri().strip_prefix("file://").unwrap())?.into_owned(); + #[cfg(windows)] + let uri = urlencoding::decode( + match self.location.as_uri().strip_prefix("file:///") { + Some(str) => str, + None => return Err("invalid path.. again?".into()) + })?.into_owned(); - let tag = match Probe::open(uri).unwrap().options(normal_options).read() { + #[cfg(unix)] + let uri = urlencoding::decode( + match self.location.as_uri().strip_prefix("file://") { + Some(str) => str, + None => return Err("invalid path.. again?".into()) + })?.into_owned(); + + let tag = match Probe::open(uri)?.options(normal_options).read() { Ok(file) => { tagged_file = file; @@ -471,26 +480,16 @@ impl Song { }; let data = tag.pictures()[index].data(); - let format = dbg!(guess_format(data)?); - let img = image::load_from_memory(data)?; - let tmp_dir = TempDir::new()?; - let fmt = match format { - Jpeg => "jpeg", - Png => "png", - _ => todo!(), - }; + let fmt = FileFormat::from_bytes(data); + let file_path = temp_dir.path().join(format!("{}_{index}.{}", self.uuid, fmt.extension())); - let file_path = tmp_dir.path().join(format!("{}.{fmt}", self.uuid)); - - open(&file_path).unwrap(); - img.save_with_format(&file_path, format).unwrap(); + File::create(&file_path)?.write_all(data)?; file_path }, }; - dbg!(open(uri)?); - + dbg!(open(dbg!(uri))?); Ok(()) } } @@ -558,6 +557,14 @@ impl URI { path_str.to_string() } + pub fn as_path(&self) -> Result<&PathBuf, Box> { + if let Self::Local(path) = self { + Ok(path) + }else { + Err("This URI is not local!".into()) + } + } + pub fn exists(&self) -> Result { match self { URI::Local(loc) => loc.try_exists(), @@ -1132,3 +1139,25 @@ impl MusicLibrary { Ok(albums) } } + +#[cfg(test)] +mod test { + use std::{path::Path, thread::sleep, time::{Duration, Instant}}; + + use tempfile::TempDir; + + use super::Song; + + + #[test] + fn get_art_test() { + let s = Song::from_file(Path::new(".\\test-config\\music\\Snail_s House - Hot Milk.mp3")).unwrap(); + let dir = &TempDir::new().unwrap(); + + let now = Instant::now(); + _ = s.open_album_art(0, dir).inspect_err(|e| println!("{e:?}")); + println!("{}ms", now.elapsed().as_millis() ); + + sleep(Duration::from_secs(1)); + } +} \ No newline at end of file From 259dbec3a0f0ad0720ae40f3557c0d6d786e721a Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sun, 24 Mar 2024 21:36:22 -0400 Subject: [PATCH 19/28] Updated album art viewer function --- src/lib.rs | 2 - src/music_controller/queue.rs | 312 ++++++++++++++++++++-------------- src/music_storage/library.rs | 75 +++++--- 3 files changed, 235 insertions(+), 154 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 379a56c..387a488 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#[allow(dead_code)] - pub mod music_storage { pub mod library; pub mod music_collection; diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 8dfae31..6180e9d 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -1,6 +1,6 @@ use uuid::Uuid; use crate::{ - music_player::Player, + music_player::{Player, PlayerError}, music_storage::library::{Album, MusicLibrary, URI} }; use std::{ @@ -8,13 +8,25 @@ use std::{ sync::{Arc, RwLock} }; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum QueueError { + #[error("Index out of bounds! Index {0} is over len {1}")] + OutOfBounds(usize, usize), + #[error("The Queue is empty!")] + EmptyQueue + +} + #[derive(Debug, PartialEq, Clone, Copy)] pub enum QueueState { Played, - Current, + First, AddHere, NoState, } + #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub enum QueueItemType<'a> { @@ -23,12 +35,20 @@ pub enum QueueItemType<'a> { Album{ album: Album<'a>, shuffled: bool, + order: Option>, // disc #, track # current: (i32, i32) }, + Playlist { + uuid: Uuid, + shuffled: bool, + order: Option>, + current: Uuid + }, None, Test } + impl QueueItemType<'_> { fn get_uri(&self, lib: Arc>) -> Option { use QueueItemType::*; @@ -42,7 +62,7 @@ impl QueueItemType<'_> { Option::None } }, - Album{album, shuffled, current: (disc, index)} => { + Album{album, shuffled, current: (disc, index), ..} => { if !shuffled { Some(album.track(*disc as usize, *index as usize).unwrap().location.clone()) }else { @@ -55,19 +75,23 @@ impl QueueItemType<'_> { } } +// TODO: move this to a different location to be used elsewhere #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] -pub enum QueueSource { +pub enum PlayerLocation { + Test, Library, Playlist(Uuid), File, + Custom } + #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub struct QueueItem<'a> { item: QueueItemType<'a>, state: QueueState, - source: QueueSource, + source: PlayerLocation, by_human: bool } impl QueueItem<'_> { @@ -75,7 +99,7 @@ impl QueueItem<'_> { QueueItem { item: QueueItemType::None, state: QueueState::NoState, - source: QueueSource::Library, + source: PlayerLocation::Library, by_human: false } } @@ -87,111 +111,83 @@ pub struct Queue<'a> { pub player: Player, pub name: String, pub items: Vec>, + pub played: Vec>, + pub loop_: bool } impl<'a> Queue<'a> { + fn has_addhere(&self) -> bool { + for item in &self.items { + if item.state == QueueState::AddHere { + return true + } + } + false + } + fn dbg_items(&self) { dbg!(self.items.iter().map(|item| item.item.clone() ).collect::>(), self.items.len()); } - pub fn new() -> Result> { + + pub fn new() -> Result { Ok( Queue { player: Player::new()?, name: String::new(), - items: Vec::new() + items: Vec::new(), + played: Vec::new(), + loop_: false, } ) } - pub fn current_index(&mut self/* , max: usize */) -> Option { - let mut e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::>().len(); - // TODO: make the max number of past songs modular - while e > 50 { - self.items.remove(0); - e -=1; - } - if e == 0 { - None - }else { - Some(e - 1) - } - } - - fn contains_state(&self, state: QueueState) -> bool { - !self.items.iter().filter(|item| item.state == state ).collect::>().is_empty() - } - - fn is_empty(&self) -> bool { - self.items.iter().filter(|item| item.state != QueueState::Played).collect::>().is_empty() - } - pub fn set_items(&mut self, tracks: Vec>) { let mut tracks = tracks; self.items.clear(); self.items.append(&mut tracks); } - pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource, by_human: bool) -> Result<(), Box> { - use QueueState::*; + pub fn add_item(&mut self, item: QueueItemType<'a>, source: PlayerLocation, by_human: bool) { let mut i: usize = 0; - let ind = self.current_index(); self.items = self.items.iter().enumerate().map(|(j, item_)| { let mut item_ = item_.to_owned(); // get the index of the current AddHere item and give it to i - if item_.state == AddHere { - i = j - ind.unwrap_or(0); - item_.state = NoState; - } else if item_.state == Current { - i = j - ind.unwrap_or(0); + if item_.state == QueueState::AddHere { + i = j; + item_.state = QueueState::NoState; } item_ }).collect::>(); - let pos = ind.unwrap_or(0) + i + if !self.is_empty() || (self.is_empty() && ind == None) { 0 } else { 1 }; - // dbg!(&pos, &i, &ind); - self.items.insert( - pos, - QueueItem { - item: item.clone(), - state: if pos == self.items.len() && i == 0 { - Current - }else { - AddHere - }, - source, - by_human - } - ); - Ok(()) + self.items.insert(i + if self.items.is_empty() { 0 } else { 1 }, QueueItem { + item, + state: QueueState::AddHere, + source, + by_human + }); } - pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: QueueSource) { + pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: PlayerLocation) { use QueueState::*; - let ind_ = self.current_index(); - let ind = ind_.unwrap_or(0); - let empty = self.is_empty(); + let empty = self.items.is_empty(); self.items.insert( - ind + if !empty && ind_ == None { 1 } else { 2 }, + (if empty { 0 } else { 1 }), QueueItem { item, - state: if empty { - Current - }else if self.items.get(ind + 1).is_none() || (!self.contains_state(AddHere) && self.items.get(ind + 1).is_some()) { - AddHere - }else { - NoState - }, + state: if (self.items.get(1).is_none() || (!self.has_addhere() && self.items.get(1).is_some()) || empty) { AddHere } else { NoState }, source, by_human: true } ) } - pub fn remove_item(&mut self, index: usize) -> Result<(), Box> { - use QueueState::*; - let remove_index: usize = (if let Some(current_index) = self.current_index() { dbg!(¤t_index); current_index } else { 0 } + index ); + pub fn add_multi(&mut self, items: Vec, source: PlayerLocation, by_human: bool) { + + } + + pub fn remove_item(&mut self, remove_index: usize) -> Result<(), QueueError> { // dbg!(/*&remove_index, self.current_index(), &index,*/ &self.items[remove_index]); @@ -199,39 +195,29 @@ impl<'a> Queue<'a> { // update the state of the next item to replace the item being removed if self.items.get(remove_index + 1).is_some() { self.items[remove_index + 1].state = self.items[remove_index].state; - }else if self.items[remove_index].state != Current { - self.items[remove_index - 1].state = self.items[remove_index].state; } - self.items[remove_index].state = NoState; + self.items[remove_index].state = QueueState::NoState; self.items.remove(remove_index); Ok(()) }else { - Err("No Songs to remove!".into()) + Err(QueueError::EmptyQueue) } } pub fn clear(&mut self) { - self.items.retain(|item| item.state == QueueState::Played ); + self.items.clear(); } pub fn clear_except(&mut self, index: usize) -> Result<(), Box> { - let mut index = index; - let ind = match self.current_index() { - Some(e) => e, - None => return Err("nothing to clear!".into()) - }; - let empty = self.is_empty(); - - if !empty { - index += ind; - }else { - index -=1 - } + use QueueState::*; + let empty = self.items.is_empty(); if !empty && index < self.items.len() { let i = self.items[index].clone(); - self.items.retain(|item| item.state == QueueState::Played || *item == i ); - self.items[ind+1].state = QueueState::Current + self.items.retain(|item| *item == i ); + self.items[0].state = AddHere; + }else if empty { + return Err("Queue is empty!".into()); }else { return Err("index out of bounds!".into()); } @@ -239,49 +225,117 @@ impl<'a> Queue<'a> { } pub fn clear_played(&mut self) { - self.items.retain(|item| item.state != QueueState::Played ); + self.played.clear(); } pub fn clear_all(&mut self) { - self.items.clear() + self.items.clear(); + self.played.clear(); } - fn move_to(&mut self, index: usize) -> Result<(), Box> { - let empty = self.is_empty(); - let nothing_error = Err("Nothing in the queue to move to!".into()); - let ind = self.current_index().unwrap_or(0); - let index = if !empty { index + ind } else { return nothing_error; }; - if !empty && index < self.items.len() -1 { + // TODO: uh, fix this? + fn move_to(&mut self, index: usize) -> Result<(), QueueError> { + use QueueState::*; + + let empty = self.items.is_empty(); + let nothing_error = Err(QueueError::EmptyQueue); + let index = if !empty { index } else { return nothing_error; }; + + if !empty && index < self.items.len() { let position = self.player.position(); if position.is_some_and(|dur| !dur.is_zero() ) { - self.items[ind].state = QueueState::Played; + self.played.push(self.items[0].clone()); } let to_item = self.items[index].clone(); - let ind = self.current_index().unwrap_or(0); loop { - if self.items[ind].item != to_item.item { + let empty = !self.items.is_empty(); + let item = self.items[0].item.to_owned(); + + if item != to_item.item && !empty { + if self.items[0].state == AddHere && self.items.get(1).is_some() { + self.items[1].state = AddHere; + } if let Err(e) = self.remove_item(0) { dbg!(&e); self.dbg_items(); return Err(e); } // dbg!(&to_item.item, &self.items[ind].item); + }else if empty { + return nothing_error; }else { break; } } }else { - return Err("index out of bounds!".into()); + return Err(QueueError::EmptyQueue.into()); } Ok(()) } - pub fn swap(&mut self, index1: usize, index2: usize) {} + pub fn swap(&mut self, a: usize, b: usize) { + self.items.swap(a, b) + } - pub fn move_item(&mut self, item: usize, to_index: usize) {} + pub fn move_item(&mut self, a: usize, b: usize) { + let item = self.items[a].to_owned(); + if a != b { + self.items.remove(a); + } + self.items.insert(b, item); + } - pub fn next() {} + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self, lib: Arc>) -> Result> { + + + + if self.items.is_empty() { + if self.loop_ { + return Err(QueueError::EmptyQueue.into()); // TODO: add function to loop the queue + }else { + return Err(QueueError::EmptyQueue.into()); + } + } + // TODO: add an algorithm to detect if the song should be skipped + let item = self.items[0].clone(); + let uri: URI = match &self.items[1].item { + QueueItemType::Song(uuid) => { + // TODO: Refactor later for multiple URIs + match &lib.read().unwrap().query_uuid(uuid) { + Some(song) => song.0.location.clone(), + None => return Err("Uuid does not exist!".into()), + } + }, + QueueItemType::Album { album, current, ..} => { + let (disc, track) = (current.0 as usize, current.1 as usize); + match album.track(disc, track) { + Some(track) => track.location.clone(), + None => return Err(format!("Track in Album {} at disc {} track {} does not exist!", album.title(), disc, track).into()) + } + }, + QueueItemType::Playlist { current, .. } => { + // TODO: Refactor later for multiple URIs + match &lib.read().unwrap().query_uuid(current) { + Some(song) => song.0.location.clone(), + None => return Err("Uuid does not exist!".into()), + } + }, + _ => todo!() + }; + if !self.player.is_paused() { + self.player.enqueue_next(&uri)?; + self.player.play()? + } + if self.items[0].state == QueueState::AddHere || !self.has_addhere() { + self.items[1].state = QueueState::AddHere; + } + self.played.push(item); + self.items.remove(0); + + Ok(todo!()) + } pub fn prev() {} @@ -293,40 +347,42 @@ impl<'a> Queue<'a> { } Ok(()) } + pub fn check_played(&mut self) { + while self.played.len() > 50 { + self.played.remove(0); + } + } +} + +pub struct OutQueue { + +} + +pub enum OutQueueItem { + } #[test] fn item_add_test() { let mut q = Queue::new().unwrap(); - dbg!(1); - for _ in 0..1 { - q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false }); - } - dbg!(2); - - // q.clear(); - dbg!(3); for _ in 0..5 { // dbg!("tick!"); - q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); + q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true); // dbg!(&q.items, &q.items.len()); } - dbg!(4); - dbg!(&q.items, &q.items.len()); - // q.clear_played(); for _ in 0..1 { q.remove_item(0).inspect_err(|e| println!("{e:?}")); } - // for _ in 0..2 { - // q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: QueueSource::Library, by_human: false }); - // } - // dbg!(5); + for _ in 0..2 { + q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: PlayerLocation::Library, by_human: false }); + } + dbg!(5); - // q.add_item_next(QueueItemType::Test, QueueSource::File); - // dbg!(6); + q.add_item_next(QueueItemType::Test, PlayerLocation::Test); + dbg!(6); dbg!(&q.items, &q.items.len()); } @@ -335,25 +391,23 @@ fn item_add_test() { fn test_() { let mut q = Queue::new().unwrap(); for _ in 0..400 { - q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false }); + q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::NoState, source: PlayerLocation::File, by_human: false }); } for _ in 0..50000 { - q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); + q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true); } - q.add_item_next(QueueItemType::Test, QueueSource::File); + // q.add_item_next(QueueItemType::Test, PlayerLocation::File); - dbg!(&q.items, &q.items.len()); + // dbg!(&q.items, &q.items.len()); } #[test] fn move_test() { let mut q = Queue::new().unwrap(); - for _ in 0..1 { - q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false }); - } + for _ in 0..5 { - q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap(); + q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true); } // q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap(); dbg!(&q.items, &q.items.len()); diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index 8c547c0..1a198f8 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -5,7 +5,9 @@ use crate::config::config::Config; // Various std things use std::collections::BTreeMap; use std::error::Error; +use std::io::Write; use std::ops::ControlFlow::{Break, Continue}; +use std::thread::sleep; // Files use file_format::{FileFormat, Kind}; @@ -14,7 +16,7 @@ use image::guess_format; use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt}; use rcue::parser::parse_from_file; use uuid::Uuid; -use std::fs; +use std::fs::{self, File}; use tempfile::TempDir; use std::path::{Path, PathBuf}; use walkdir::WalkDir; @@ -160,11 +162,6 @@ pub struct Song { pub tags: BTreeMap, } -#[test] -fn get_art_test() { - let s = Song::from_file(Path::new("F:\\Music\\Mp3\\ななひら\\Colory Starry\\05 - Fly Away!.mp3")).unwrap(); - s.open_album_art(0).inspect_err(|e| println!("{e:?}")).unwrap(); -} impl Song { /// Get a tag's value @@ -431,7 +428,7 @@ impl Song { Ok(tracks) } - pub fn open_album_art(&self, index: usize) -> Result<(), Box> { + pub fn open_album_art(&self, index: usize, temp_dir: &TempDir) -> Result<(), Box> { use opener::open; use urlencoding::decode; @@ -451,9 +448,21 @@ impl Song { let blank_tag = &lofty::Tag::new(TagType::Id3v2); let tagged_file: lofty::TaggedFile; - let uri = urlencoding::decode(self.location.as_uri().strip_prefix("file://").unwrap())?.into_owned(); + #[cfg(windows)] + let uri = urlencoding::decode( + match self.location.as_uri().strip_prefix("file:///") { + Some(str) => str, + None => return Err("invalid path.. again?".into()) + })?.into_owned(); - let tag = match Probe::open(uri).unwrap().options(normal_options).read() { + #[cfg(unix)] + let uri = urlencoding::decode( + match self.location.as_uri().strip_prefix("file://") { + Some(str) => str, + None => return Err("invalid path.. again?".into()) + })?.into_owned(); + + let tag = match Probe::open(uri)?.options(normal_options).read() { Ok(file) => { tagged_file = file; @@ -471,26 +480,16 @@ impl Song { }; let data = tag.pictures()[index].data(); - let format = dbg!(guess_format(data)?); - let img = image::load_from_memory(data)?; - let tmp_dir = TempDir::new()?; - let fmt = match format { - Jpeg => "jpeg", - Png => "png", - _ => todo!(), - }; + let fmt = FileFormat::from_bytes(data); + let file_path = temp_dir.path().join(format!("{}_{index}.{}", self.uuid, fmt.extension())); - let file_path = tmp_dir.path().join(format!("{}.{fmt}", self.uuid)); - - open(&file_path).unwrap(); - img.save_with_format(&file_path, format).unwrap(); + File::create(&file_path)?.write_all(data)?; file_path }, }; - dbg!(open(uri)?); - + dbg!(open(dbg!(uri))?); Ok(()) } } @@ -558,6 +557,14 @@ impl URI { path_str.to_string() } + pub fn as_path(&self) -> Result<&PathBuf, Box> { + if let Self::Local(path) = self { + Ok(path) + }else { + Err("This URI is not local!".into()) + } + } + pub fn exists(&self) -> Result { match self { URI::Local(loc) => loc.try_exists(), @@ -1132,3 +1139,25 @@ impl MusicLibrary { Ok(albums) } } + +#[cfg(test)] +mod test { + use std::{path::Path, thread::sleep, time::{Duration, Instant}}; + + use tempfile::TempDir; + + use super::Song; + + + #[test] + fn get_art_test() { + let s = Song::from_file(Path::new(".\\test-config\\music\\Snail_s House - Hot Milk.mp3")).unwrap(); + let dir = &TempDir::new().unwrap(); + + let now = Instant::now(); + _ = s.open_album_art(0, dir).inspect_err(|e| println!("{e:?}")); + println!("{}ms", now.elapsed().as_millis() ); + + sleep(Duration::from_secs(1)); + } +} \ No newline at end of file From 1734a39db566cb349a9127dcfd38c02adbd775ea Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sun, 24 Mar 2024 21:40:19 -0400 Subject: [PATCH 20/28] removed `image` crate --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a3c737b..266e022 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,5 +37,4 @@ uuid = { version = "1.6.1", features = ["v4", "serde"]} serde_json = "1.0.111" deunicode = "1.4.2" opener = { version = "0.7.0", features = ["reveal"]} -image = "0.25.0" tempfile = "3.10.1" From 50f42e3a265c1e698537861d4ef1422bcd9bec10 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sun, 24 Mar 2024 21:41:43 -0400 Subject: [PATCH 21/28] removed `image` crate --- Cargo.toml | 1 - src/music_storage/library.rs | 2 -- 2 files changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a3c737b..266e022 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,5 +37,4 @@ uuid = { version = "1.6.1", features = ["v4", "serde"]} serde_json = "1.0.111" deunicode = "1.4.2" opener = { version = "0.7.0", features = ["reveal"]} -image = "0.25.0" tempfile = "3.10.1" diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index 1a198f8..2095821 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -12,7 +12,6 @@ use std::thread::sleep; // Files use file_format::{FileFormat, Kind}; use glib::filename_to_uri; -use image::guess_format; use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt}; use rcue::parser::parse_from_file; use uuid::Uuid; @@ -20,7 +19,6 @@ use std::fs::{self, File}; use tempfile::TempDir; use std::path::{Path, PathBuf}; use walkdir::WalkDir; -use image::ImageFormat::*; // Time use chrono::{serde::ts_milliseconds_option, DateTime, Utc}; From 7da0b1a1dbff0af4e5ae1c6d02c1b1a82cc0ae8f Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sat, 30 Mar 2024 00:55:56 -0400 Subject: [PATCH 22/28] updated dependencies and made small changes to the library --- Cargo.toml | 7 +++---- src/music_storage/library.rs | 13 ++++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 266e022..2d106a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,9 +32,8 @@ leb128 = "0.2.5" urlencoding = "2.1.3" m3u8-rs = "5.0.5" thiserror = "1.0.56" -font = "0.27.0" -uuid = { version = "1.6.1", features = ["v4", "serde"]} +uuid = { version = "1.6.1", features = ["v4", "serde"] } serde_json = "1.0.111" deunicode = "1.4.2" -opener = { version = "0.7.0", features = ["reveal"]} -tempfile = "3.10.1" +opener = { version = "0.7.0", features = ["reveal"] } +tempfile = "3.10.1" \ No newline at end of file diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index 2095821..1afc991 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -7,7 +7,7 @@ use std::collections::BTreeMap; use std::error::Error; use std::io::Write; use std::ops::ControlFlow::{Break, Continue}; -use std::thread::sleep; + // Files use file_format::{FileFormat, Kind}; @@ -426,6 +426,8 @@ impl Song { Ok(tracks) } + /// Takes the AlbumArt[index] and opens it in the native file viewer + pub fn open_album_art(&self, index: usize, temp_dir: &TempDir) -> Result<(), Box> { use opener::open; use urlencoding::decode; @@ -446,14 +448,14 @@ impl Song { let blank_tag = &lofty::Tag::new(TagType::Id3v2); let tagged_file: lofty::TaggedFile; - #[cfg(windows)] + #[cfg(target_family = "windows")] let uri = urlencoding::decode( match self.location.as_uri().strip_prefix("file:///") { Some(str) => str, None => return Err("invalid path.. again?".into()) })?.into_owned(); - #[cfg(unix)] + #[cfg(target_family = "unix")] let uri = urlencoding::decode( match self.location.as_uri().strip_prefix("file://") { Some(str) => str, @@ -1149,13 +1151,14 @@ mod test { #[test] fn get_art_test() { - let s = Song::from_file(Path::new(".\\test-config\\music\\Snail_s House - Hot Milk.mp3")).unwrap(); + let s = Song::from_file(Path::new("")).unwrap(); let dir = &TempDir::new().unwrap(); let now = Instant::now(); _ = s.open_album_art(0, dir).inspect_err(|e| println!("{e:?}")); + _ = s.open_album_art(1, dir).inspect_err(|e| println!("{e:?}")); println!("{}ms", now.elapsed().as_millis() ); - sleep(Duration::from_secs(1)); + sleep(Duration::from_secs(20)); } } \ No newline at end of file From f02f5bca41c75dd022e25c11b3d60c477ad66c3f Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Wed, 3 Apr 2024 01:00:52 -0400 Subject: [PATCH 23/28] first draft of library Rework --- Cargo.toml | 3 +- src/music_controller/controller.rs | 2 +- src/music_controller/queue.rs | 11 +- src/music_storage/db_reader/foobar/reader.rs | 6 +- src/music_storage/db_reader/itunes/reader.rs | 17 ++- src/music_storage/library.rs | 127 ++++++++++++++----- src/music_storage/playlist.rs | 40 +++++- 7 files changed, 149 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2d106a4..3c7d698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,4 +36,5 @@ uuid = { version = "1.6.1", features = ["v4", "serde"] } serde_json = "1.0.111" deunicode = "1.4.2" opener = { version = "0.7.0", features = ["reveal"] } -tempfile = "3.10.1" \ No newline at end of file +tempfile = "3.10.1" +nestify = "0.3.3" diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index ebcc27f..ca14d12 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -314,7 +314,7 @@ mod tests { a.q_set_volume(i, 0.04); // a.new_queue(); let songs = a.lib_get_songs(); - a.q_enqueue(i, songs[2].location.clone()); + a.q_enqueue(i, songs[2].primary_uri().unwrap().0.clone()); // a.enqueue(1, songs[2].location.clone()); a.q_play(i).unwrap(); // a.play(1).unwrap(); diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 6180e9d..78bbe7c 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -57,14 +57,14 @@ impl QueueItemType<'_> { match self { Song(uuid) => { if let Some((song, _)) = lib.query_uuid(uuid) { - Some(song.location.clone()) + Some(song.primary_uri().unwrap().0.clone()) // TODO: error handle these better }else { Option::None } }, Album{album, shuffled, current: (disc, index), ..} => { if !shuffled { - Some(album.track(*disc as usize, *index as usize).unwrap().location.clone()) + Some(album.track(*disc as usize, *index as usize).unwrap().primary_uri().unwrap().0.clone()) }else { todo!() } @@ -302,23 +302,22 @@ impl<'a> Queue<'a> { let item = self.items[0].clone(); let uri: URI = match &self.items[1].item { QueueItemType::Song(uuid) => { - // TODO: Refactor later for multiple URIs match &lib.read().unwrap().query_uuid(uuid) { - Some(song) => song.0.location.clone(), + Some(song) => song.0.primary_uri()?.0.clone(), None => return Err("Uuid does not exist!".into()), } }, QueueItemType::Album { album, current, ..} => { let (disc, track) = (current.0 as usize, current.1 as usize); match album.track(disc, track) { - Some(track) => track.location.clone(), + Some(track) => track.primary_uri()?.0.clone(), None => return Err(format!("Track in Album {} at disc {} track {} does not exist!", album.title(), disc, track).into()) } }, QueueItemType::Playlist { current, .. } => { // TODO: Refactor later for multiple URIs match &lib.read().unwrap().query_uuid(current) { - Some(song) => song.0.location.clone(), + Some(song) => song.0.primary_uri()?.0.clone(), None => return Err("Uuid does not exist!".into()), } }, diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs index 815f9a7..4d07e71 100644 --- a/src/music_storage/db_reader/foobar/reader.rs +++ b/src/music_storage/db_reader/foobar/reader.rs @@ -176,14 +176,15 @@ pub struct FoobarPlaylistTrack { impl FoobarPlaylistTrack { fn find_song(&self) -> Song { let location = URI::Local(self.file_name.clone().into()); + let internal_tags = Vec::new(); Song { - location, + location: vec![location], uuid: Uuid::new_v4(), plays: 0, skips: 0, favorited: false, - // banned: None, + banned: None, rating: None, format: None, duration: self.duration, @@ -193,6 +194,7 @@ impl FoobarPlaylistTrack { date_modified: None, album_art: Vec::new(), tags: BTreeMap::new(), + internal_tags, } } } diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs index 90a48f2..f6032db 100644 --- a/src/music_storage/db_reader/itunes/reader.rs +++ b/src/music_storage/db_reader/itunes/reader.rs @@ -13,7 +13,7 @@ use std::vec::Vec; use chrono::prelude::*; use crate::music_storage::db_reader::extern_library::ExternalLibrary; -use crate::music_storage::library::{AlbumArt, Service, Song, Tag, URI}; +use crate::music_storage::library::{AlbumArt, BannedType, Service, Song, Tag, URI}; use crate::music_storage::utils; use urlencoding::decode; @@ -166,17 +166,19 @@ impl ExternalLibrary for ITunesLibrary { }; let play_time_ = StdDur::from_secs(track.plays as u64 * dur.as_secs()); + let internal_tags = Vec::new(); // TODO: handle internal tags generation + let ny: Song = Song { - location, + location: vec![location], uuid: Uuid::new_v4(), plays: track.plays, skips: 0, favorited: track.favorited, - // banned: if track.banned { - // Some(BannedType::All) - // }else { - // None - // }, + banned: if track.banned { + Some(BannedType::All) + }else { + None + }, rating: track.rating, format: match FileFormat::from_file(PathBuf::from(&loc)) { Ok(e) => Some(e), @@ -192,6 +194,7 @@ impl ExternalLibrary for ITunesLibrary { Err(_) => Vec::new(), }, tags: tags_, + internal_tags, }; // dbg!(&ny.tags); bun.push(ny); diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index 1afc991..ab2c231 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -1,3 +1,4 @@ +use super::playlist::PlaylistFolder; // Crate things use super::utils::{find_images, normalize, read_file, write_file}; use crate::config::config::Config; @@ -117,35 +118,58 @@ impl ToString for Field { } } +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum InternalTag { + DoNotTrack(DoNotTrack), + SongType(SongType), + SongLink(Uuid, SongType), + // Volume Adjustment from -100% to 100% + VolumeAdjustment(i8), +} + #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[non_exhaustive] pub enum BannedType { Shuffle, All, } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] pub enum DoNotTrack { // TODO: add services to not track + LastFM, + LibreFM, + MusicBrainz, + Discord, } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] -enum SongType { - // TODO: add song types +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum SongType { + // TODO: add MORE?! song types Main, Instrumental, Remix, Custom(String) } +impl Default for SongType { + fn default() -> Self { + SongType::Main + } +} + /// Stores information about a single song #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct Song { - pub location: URI, + pub location: Vec, pub uuid: Uuid, pub plays: i32, pub skips: i32, pub favorited: bool, - // pub banned: Option, + pub banned: Option, pub rating: Option, pub format: Option, pub duration: Duration, @@ -158,6 +182,7 @@ pub struct Song { pub date_modified: Option>, pub album_art: Vec, pub tags: BTreeMap, + pub internal_tags: Vec } @@ -180,7 +205,7 @@ impl Song { pub fn get_field(&self, target_field: &str) -> Option { let lower_target = target_field.to_lowercase(); match lower_target.as_str() { - "location" => Some(Field::Location(self.location.clone())), + "location" => Some(Field::Location(self.primary_uri().unwrap().0.clone())), //TODO: make this not unwrap() "plays" => Some(Field::Plays(self.plays)), "skips" => Some(Field::Skips(self.skips)), "favorited" => Some(Field::Favorited(self.favorited)), @@ -279,13 +304,15 @@ impl Song { // TODO: Fix error handling let binding = fs::canonicalize(target_file).unwrap(); + // TODO: Handle creation of internal tag: Song Type and Song Links + let internal_tags = { Vec::new() }; let new_song = Song { - location: URI::Local(binding), + location: vec![URI::Local(binding)], uuid: Uuid::new_v4(), plays: 0, skips: 0, favorited: false, - // banned: None, + banned: None, rating: None, format, duration, @@ -295,6 +322,7 @@ impl Song { date_modified: Some(chrono::offset::Utc::now()), tags, album_art, + internal_tags, }; Ok(new_song) } @@ -399,17 +427,17 @@ impl Song { let album_art = find_images(&audio_location.to_path_buf()).unwrap(); let new_song = Song { - location: URI::Cue { + location: vec![URI::Cue { location: audio_location.clone(), index: i, start, end, - }, + }], uuid: Uuid::new_v4(), plays: 0, skips: 0, favorited: false, - // banned: None, + banned: None, rating: None, format, duration, @@ -419,6 +447,7 @@ impl Song { date_modified: Some(chrono::offset::Utc::now()), tags, album_art, + internal_tags: Vec::new() }; tracks.push((new_song, audio_location.clone())); } @@ -448,16 +477,17 @@ impl Song { let blank_tag = &lofty::Tag::new(TagType::Id3v2); let tagged_file: lofty::TaggedFile; + // TODO: add support for other URI types... or don't #[cfg(target_family = "windows")] let uri = urlencoding::decode( - match self.location.as_uri().strip_prefix("file:///") { + match self.primary_uri()?.0.as_uri().strip_prefix("file:///") { Some(str) => str, None => return Err("invalid path.. again?".into()) })?.into_owned(); #[cfg(target_family = "unix")] let uri = urlencoding::decode( - match self.location.as_uri().strip_prefix("file://") { + match self.primary_uri()?.as_uri().strip_prefix("file://") { Some(str) => str, None => return Err("invalid path.. again?".into()) })?.into_owned(); @@ -492,6 +522,26 @@ impl Song { dbg!(open(dbg!(uri))?); Ok(()) } + + /// Returns a reference to the first valid URI in the song, and any invalid URIs that come before it, or errors if there are no valid URIs + #[allow(clippy::type_complexity)] + pub fn primary_uri(&self) -> Result<(&URI, Option>), Box> { + let mut invalid_uris = Vec::new(); + let mut valid_uri = None; + + for uri in &self.location { + if uri.exists()? { + valid_uri = Some(uri); + break; + }else { + invalid_uris.push(uri); + } + } + match valid_uri { + Some(uri) => Ok((uri, if !invalid_uris.is_empty() { Some(invalid_uris) } else { None } )), + None => Err("No valid URIs for this song".into()) + } + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -652,14 +702,8 @@ pub struct MusicLibrary { pub name: String, pub uuid: Uuid, pub library: Vec, -} - -#[test] -fn library_init() { - let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap(); - let target_uuid = config.libraries.libraries[0].uuid; - let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap(); - dbg!(a); + pub playlists: PlaylistFolder, + pub backup_songs: Vec // maybe move this to the config instead? } impl MusicLibrary { @@ -669,6 +713,8 @@ impl MusicLibrary { name, uuid, library: Vec::new(), + playlists: PlaylistFolder::new(), + backup_songs: Vec::new(), } } @@ -736,8 +782,11 @@ impl MusicLibrary { .par_iter() .enumerate() .try_for_each(|(i, track)| { - if path == &track.location { - return std::ops::ControlFlow::Break((track, i)); + for location in &track.location { + //TODO: check that this works + if path == location { + return std::ops::ControlFlow::Break((track, i)); + } } Continue(()) }); @@ -773,7 +822,7 @@ impl MusicLibrary { fn query_path(&self, path: PathBuf) -> Option> { let result: Arc>> = Arc::new(Mutex::new(Vec::new())); self.library.par_iter().for_each(|track| { - if path == track.location.path() { + if path == track.primary_uri().unwrap().0.path() { //TODO: make this also not unwrap result.clone().lock().unwrap().push(track); } }); @@ -885,13 +934,14 @@ impl MusicLibrary { } pub fn add_song(&mut self, new_song: Song) -> Result<(), Box> { - if self.query_uri(&new_song.location).is_some() { - return Err(format!("URI already in database: {:?}", new_song.location).into()); + let location = new_song.primary_uri()?.0; + if self.query_uri(location).is_some() { + return Err(format!("URI already in database: {:?}", location).into()); } - match new_song.location { - URI::Local(_) if self.query_path(new_song.location.path()).is_some() => { - return Err(format!("Location exists for {:?}", new_song.location).into()) + match location { + URI::Local(_) if self.query_path(location.path()).is_some() => { + return Err(format!("Location exists for {:?}", location).into()) } _ => (), } @@ -914,17 +964,18 @@ impl MusicLibrary { } /// Scan the song by a location and update its tags + // TODO: change this to work with multiple uris pub fn update_uri( &mut self, target_uri: &URI, new_tags: Vec, ) -> Result<(), Box> { - let target_song = match self.query_uri(target_uri) { + let (target_song, _) = match self.query_uri(target_uri) { Some(song) => song, None => return Err("URI not in database!".to_string().into()), }; - println!("{:?}", target_song.0.location); + println!("{:?}", target_song.location); for tag in new_tags { println!("{:?}", tag); @@ -1142,10 +1193,12 @@ impl MusicLibrary { #[cfg(test)] mod test { - use std::{path::Path, thread::sleep, time::{Duration, Instant}}; + use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}, thread::sleep, time::{Duration, Instant}}; use tempfile::TempDir; + use crate::{config::config::Config, music_storage::library::MusicLibrary}; + use super::Song; @@ -1161,4 +1214,12 @@ mod test { sleep(Duration::from_secs(20)); } + + #[test] + fn library_init() { + let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap(); + let target_uuid = config.libraries.libraries[0].uuid; + let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap(); + dbg!(a); + } } \ No newline at end of file diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs index 783a293..eb4fef4 100644 --- a/src/music_storage/playlist.rs +++ b/src/music_storage/playlist.rs @@ -1,17 +1,43 @@ -use std::{fs::File, io::{Read, Error}}; +use std::{fs::File, io::{Error, Read}, time::Duration}; -use chrono::Duration; +// use chrono::Duration; +use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::library::{AlbumArt, Song, Tag}; use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2}; +use nestify::nest; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub enum SortOrder { Manual, Tag(Tag) } -#[derive(Debug, Clone)] + +nest! { + #[derive(Debug, Clone, Deserialize, Serialize)]* + pub struct PlaylistFolder { + name: String, + items: Vec< + pub enum PlaylistFolderItem { + Folder(PlaylistFolder), + List(Playlist) + } + > + } +} + +impl PlaylistFolder { + pub fn new() -> Self { + PlaylistFolder { + name: String::new(), + items: Vec::new(), + } + } +} + + +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct Playlist { uuid: Uuid, title: String, @@ -28,7 +54,7 @@ impl Playlist { pub fn play_count(&self) -> i32 { self.play_count } - pub fn play_time(&self) -> chrono::Duration { + pub fn play_time(&self) -> Duration { self.play_time } pub fn set_tracks(&mut self, tracks: Vec) { @@ -71,7 +97,7 @@ impl Playlist { |track| { MediaSegment { - uri: track.location.to_string().into(), + uri: track.primary_uri().unwrap().0.to_string().into(), // TODO: error handle this better duration: track.duration.as_millis() as f32, title: Some(track.tags.get_key_value(&Tag::Title).unwrap().1.into()), ..Default::default() @@ -148,7 +174,7 @@ impl Default for Playlist { tracks: Vec::default(), sort_order: SortOrder::Manual, play_count: 0, - play_time: Duration::zero(), + play_time: Duration::from_secs(0), } } } From a75081d4fc4f10e08141a1abc59c85cc214a1546 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Fri, 5 Apr 2024 02:09:15 -0400 Subject: [PATCH 24/28] added m3u8 to playlist function and fixed playlist to m3u8 function --- src/config/config.rs | 64 +++++++------- src/music_storage/library.rs | 1 + src/music_storage/playlist.rs | 151 ++++++++++++++++++++++++---------- 3 files changed, 142 insertions(+), 74 deletions(-) diff --git a/src/config/config.rs b/src/config/config.rs index 76c59cc..5a209ae 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -69,10 +69,10 @@ impl ConfigLibraries { } pub fn get_library(&self, uuid: &Uuid) -> Result { - dbg!(&uuid); + for library in &self.libraries { + // dbg!(&library.uuid, &uuid); if &library.uuid == uuid { - dbg!(&library.uuid); return Ok(library.to_owned()) } } @@ -148,6 +148,13 @@ impl Config { let config: Config = serde_json::from_str::(&bun)?; Ok(config) } + + pub fn push_library(&mut self, lib: ConfigLibrary) { + if self.libraries.libraries.is_empty() { + self.libraries.default_library = lib.uuid; + } + self.libraries.libraries.push(lib); + } } #[derive(Error, Debug)] @@ -165,45 +172,42 @@ pub enum ConfigError { } #[cfg(test)] -mod tests { +pub mod tests { use std::{path::PathBuf, sync::{Arc, RwLock}}; use crate::music_storage::library::MusicLibrary; use super::{Config, ConfigLibraries, ConfigLibrary}; - #[test] - fn config_test() { - let lib_a = ConfigLibrary::new(PathBuf::from("test-config/library1"), String::from("library1"), None); - let lib_b = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None); - let lib_c = ConfigLibrary::new(PathBuf::from("test-config/library3"), String::from("library3"), None); - let config = Config { + pub fn new_config_lib() -> (Config, MusicLibrary) { + let lib = ConfigLibrary::new(PathBuf::from("test-config/library"), String::from("library"), None); + let mut config = Config { path: PathBuf::from("test-config/config_test.json"), - libraries: ConfigLibraries { - libraries: vec![ - lib_a.clone(), - lib_b.clone(), - lib_c.clone(), - ], - ..Default::default() - }, ..Default::default() }; - config.write_file(); - let arc = Arc::new(RwLock::from(config)); - MusicLibrary::init(arc.clone(), lib_a.uuid).unwrap(); - MusicLibrary::init(arc.clone(), lib_b.uuid).unwrap(); - MusicLibrary::init(arc.clone(), lib_c.uuid).unwrap(); - } + config.push_library(lib); + config.write_file().unwrap(); - #[test] - fn test2() { - let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); - let uuid = config.libraries.get_default().unwrap().uuid; - let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap(); + let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), dbg!(config.libraries.default_library)).unwrap(); lib.scan_folder("test-config/music/").unwrap(); lib.save(config.clone()).unwrap(); - dbg!(&lib); - dbg!(&config); + + (config, lib) + } + + pub fn read_config_lib() -> (Config, MusicLibrary) { + let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); + + // dbg!(&config); + + let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), config.libraries.get_default().unwrap().uuid).unwrap(); + + + lib.scan_folder("test-config/music/").unwrap(); + + lib.save(config.clone()).unwrap(); + + + (config, lib) } #[test] diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index 1afc991..22452a9 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -686,6 +686,7 @@ impl MusicLibrary { false => { // If the library does not exist, re-create it let lib = MusicLibrary::new(String::new(), uuid); + write_file(&lib, path)?; lib } diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs index 783a293..bd40b6a 100644 --- a/src/music_storage/playlist.rs +++ b/src/music_storage/playlist.rs @@ -1,8 +1,9 @@ -use std::{fs::File, io::{Read, Error}}; +use std::{fs::File, io::Read, path:: PathBuf, sync::{Arc, RwLock}}; +use std::error::Error; use chrono::Duration; use uuid::Uuid; -use super::library::{AlbumArt, Song, Tag}; +use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI}; use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2}; @@ -34,16 +35,14 @@ impl Playlist { pub fn set_tracks(&mut self, tracks: Vec) { self.tracks = tracks; } - pub fn add_track(&mut self, track: Uuid) -> Result<(), Error> { + pub fn add_track(&mut self, track: Uuid) { self.tracks.push(track); - Ok(()) } - pub fn remove_track(&mut self, index: i32) -> Result<(), Error> { + pub fn remove_track(&mut self, index: i32) { let index = index as usize; if (self.tracks.len() - 1) >= index { self.tracks.remove(index); } - Ok(()) } // pub fn get_index(&self, song_name: &str) -> Option { // let mut index = 0; @@ -58,26 +57,41 @@ impl Playlist { // } // None // } - pub fn contains_value(&self, tag: &Tag, value: &str) -> bool { - &self.tracks.iter().for_each(|track| { + pub fn contains_value(&self, tag: &Tag, value: &String, lib: Arc>) -> bool { + let lib = lib.read().unwrap(); + let items = match lib.query_tracks(value, &vec![tag.to_owned()], &vec![tag.to_owned()]) { + Some(e) => e, + None => return false + }; + + for item in items { + for uuid in &self.tracks { + if uuid == &item.uuid { + return true; + } + } + } - }); false } - pub fn to_m3u8(&mut self, tracks: Vec) { - let seg = tracks - .iter() - .map({ - |track| { - MediaSegment { - uri: track.location.to_string().into(), - duration: track.duration.as_millis() as f32, - title: Some(track.tags.get_key_value(&Tag::Title).unwrap().1.into()), - ..Default::default() - } + pub fn to_m3u8(&mut self, lib: Arc>, location: &str) -> Result<(), Box> { + let lib = lib.read().unwrap(); + let seg = self.tracks + .iter() + .filter_map( |uuid| { + if let Some((track, _)) = lib.query_uuid(uuid) { + if let URI::Local(_) = track.location { + Some(MediaSegment { + uri: track.location.to_string(), + duration: track.duration.as_millis() as f32, + title: track.tags.get_key_value(&Tag::Title).map(|tag| tag.1.into()), + ..Default::default() + }) + }else { None } + }else { None } } - }) + ) .collect::>(); let m3u8 = MediaPlaylist { @@ -90,19 +104,21 @@ impl Playlist { segments: seg.clone(), ..Default::default() }; - //TODO: change this to put in a real file path + let mut file = std::fs::OpenOptions::new() .read(true) .create(true) + .truncate(true) .write(true) - .open("F:\\Dango Music Player\\playlist.m3u8") - .unwrap(); - m3u8.write_to(&mut file).unwrap(); + .open(location)?; + m3u8.write_to(&mut file)?; + Ok(()) } - pub fn from_m3u8(path: &str) -> Result { + + pub fn from_m3u8(path: &str, lib: Arc>) -> Result> { let mut file = match File::open(path) { Ok(file) => file, - Err(e) => return Err(e), + Err(e) => return Err(e.into()), }; let mut bytes = Vec::new(); file.read_to_end(&mut bytes).unwrap(); @@ -110,18 +126,53 @@ impl Playlist { let parsed = m3u8_rs::parse_playlist(&bytes); let playlist = match parsed { - Result::Ok((i, playlist)) => playlist, + Result::Ok((_, playlist)) => playlist, Result::Err(e) => panic!("Parsing error: \n{}", e), }; match playlist { - List2::MasterPlaylist(_) => panic!(), - List2::MediaPlaylist(pl) => { - let values = pl.segments.iter().map(|seg| seg.uri.to_owned() ).collect::>(); + List2::MasterPlaylist(_) => Err("This is a Master Playlist!\nPlase input a Media Playlist".into()), + List2::MediaPlaylist(playlist_) => { + let mut uuids = Vec::new(); + for seg in playlist_.segments { + let path_ = PathBuf::from(seg.uri.to_owned()); + let mut lib = lib.write().unwrap(); + + let uuid = if let Some((song, _)) = lib.query_uri(&URI::Local(path_.clone())) { + song.uuid + }else { + let song_ = Song::from_file(&path_)?; + let uuid = song_.uuid.to_owned(); + lib.add_song(song_)?; + uuid + }; + uuids.push(uuid); + } + let mut playlist = Playlist::new(); + + #[cfg(target_family = "windows")] + { + playlist.title = path.split("\\") + .last() + .unwrap_or_default() + .strip_suffix(".m3u8") + .unwrap_or_default() + .to_string(); + } + #[cfg(target_family = "unix")] + { + playlist.title = path.split("/") + .last() + .unwrap_or_default() + .strip_suffix(".m3u8") + .unwrap_or_default() + .to_string(); + } + + playlist.set_tracks(uuids); + Ok(playlist) } } - - todo!() } fn title(&self) -> &String { &self.title @@ -153,14 +204,26 @@ impl Default for Playlist { } } -// #[test] -// fn list_to_m3u8() { -// let lib = ITunesLibrary::from_file(Path::new( -// "F:\\Music\\Mp3\\Music Main\\iTunes Music Library.xml", -// )); -// let mut a = Playlist::new(); -// let c = lib.to_songs(); -// let mut b = c.iter().map(|song| song.to_owned()).collect::>(); -// a.tracks.append(&mut b); -// a.to_m3u8() -// } +#[cfg(test)] +mod test_super { + use super::*; + use crate::config::config::tests::read_config_lib; + + #[test] + fn list_to_m3u8() { + let (_, lib) = read_config_lib(); + let mut playlist = Playlist::new(); + let tracks = lib.library.iter().map(|track| track.uuid ).collect(); + playlist.set_tracks(tracks); + + _ = playlist.to_m3u8(Arc::new(RwLock::from(lib)), ".\\test-config\\playlists\\playlist.m3u8"); + } + + #[test] + fn m3u8_to_list() { + let (_, lib) = read_config_lib(); + let arc = Arc::new(RwLock::from(lib)); + let playlist = Playlist::from_m3u8(".\\test-config\\playlists\\playlist.m3u8", arc).unwrap(); + dbg!(playlist); + } +} From ab529f4c9b82e1a376d93fbccaf86ec5dad0a5d6 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Fri, 5 Apr 2024 20:26:13 -0400 Subject: [PATCH 25/28] added `out_queue` function, rudamentary listenbrainz scrobbling and other small changes --- Cargo.toml | 3 +- src/config/config.rs | 15 +++- src/music_controller/connections.rs | 93 +++++++++++++++++++- src/music_controller/controller.rs | 47 +++++----- src/music_controller/queue.rs | 8 +- src/music_storage/playlist.rs | 132 +++++++++++++++++++++++----- 6 files changed, 244 insertions(+), 54 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2d106a4..49ac4e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,4 +36,5 @@ uuid = { version = "1.6.1", features = ["v4", "serde"] } serde_json = "1.0.111" deunicode = "1.4.2" opener = { version = "0.7.0", features = ["reveal"] } -tempfile = "3.10.1" \ No newline at end of file +tempfile = "3.10.1" +listenbrainz = "0.7.0" diff --git a/src/config/config.rs b/src/config/config.rs index 5a209ae..1c19b15 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -90,11 +90,18 @@ impl ConfigLibraries { } #[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct ConfigConnections { + pub listenbrainz_token: Option +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[serde(default)] pub struct Config { pub path: PathBuf, pub backup_folder: Option, pub libraries: ConfigLibraries, pub volume: f32, + pub connections: ConfigConnections, } impl Config { @@ -212,10 +219,10 @@ pub mod tests { #[test] fn test3() { - let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); - let uuid = config.libraries.get_default().unwrap().uuid; - let lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap(); + let (config, lib) = read_config_lib(); - dbg!(lib); + _ = config.write_file(); + + dbg!(config); } } diff --git a/src/music_controller/connections.rs b/src/music_controller/connections.rs index ac4abd9..dec723c 100644 --- a/src/music_controller/connections.rs +++ b/src/music_controller/connections.rs @@ -1,6 +1,95 @@ -use super::controller::Controller; +use std::{ + sync::{Arc, RwLock}, + error::Error, +}; + +use listenbrainz::ListenBrainz; +use uuid::Uuid; + +use crate::{ + config::config::Config, music_controller::controller::{Controller, QueueCmd, QueueResponse}, music_storage::library::{MusicLibrary, Song, Tag} +}; + +use super::controller::DatabaseResponse; + impl Controller { - //more stuff goes here + pub fn listenbrainz_authenticate(&mut self) -> Result> { + let config = &self.config.read().unwrap(); + let mut client = ListenBrainz::new(); + + let lbz_token = match &config.connections.listenbrainz_token { + Some(token) => token, + None => todo!("No ListenBrainz token in config") + }; + + if !client.is_authenticated() { + client.authenticate(lbz_token)?; + } + + Ok(client) + } + pub fn lbz_scrobble(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box> { + let config = &self.config.read().unwrap(); + + &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid)); + let res = &self.db_mail.recv()?; + let song = match res { + DatabaseResponse::Song(song) => song, + _ => todo!() + }; + let unknown = &"unknown".to_string(); + let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown); + let track = song.get_tag(&Tag::Title).unwrap_or(unknown); + let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str()); + + client.listen(artist, track, release)?; + Ok(()) + } + + pub fn lbz_now_playing(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box> { + let config = &self.config.read().unwrap(); + + &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid)); + let res = &self.db_mail.recv()?; + let song = match res { + DatabaseResponse::Song(song) => song, + _ => todo!() + }; + let unknown = &"unknown".to_string(); + let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown); + let track = song.get_tag(&Tag::Title).unwrap_or(unknown); + let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str()); + + client.listen(artist, track, release)?; + Ok(()) + } +} + +#[cfg(test)] +mod test_super { + use std::{thread::sleep, time::Duration}; + + use super::*; + use crate::config::config::tests::read_config_lib; + + #[test] + fn listenbrainz() { + let mut c = Controller::start(".\\test-config\\config_test.json").unwrap(); + + let client = c.listenbrainz_authenticate().unwrap(); + + c.q_new().unwrap(); + c.queue_mail[0].send(QueueCmd::SetVolume(0.04)).unwrap(); + + let songs = c.lib_get_songs(); + + c.q_enqueue(0, songs[1].location.to_owned()).unwrap(); + c.q_play(0).unwrap(); + + + sleep(Duration::from_secs(100)); + c.lbz_scrobble(client, songs[1].uuid).unwrap(); + } } diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index ebcc27f..d13a62f 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -6,12 +6,14 @@ use std::path::PathBuf; use std::sync::{Arc, RwLock}; use crossbeam_channel::{Sender, Receiver}; use crossbeam_channel; +use listenbrainz::ListenBrainz; use std::thread::spawn; use std::error::Error; use crossbeam_channel::unbounded; use uuid::Uuid; +use crate::music_controller::queue::{QueueItem, QueueItemType}; use crate::music_storage::library::{Tag, URI}; use crate::{ music_storage::library::{MusicLibrary, Song}, @@ -21,27 +23,27 @@ use crate::{ pub struct Controller { // queues: Vec, - config: Arc>, + pub config: Arc>, // library: MusicLibrary, - controller_mail: MailMan, - db_mail: MailMan, - queue_mail: Vec>, + pub(super) controller_mail: MailMan, + pub(super) db_mail: MailMan, + pub(super) queue_mail: Vec>, } #[derive(Debug)] -pub enum ControllerCmd { +pub(super) enum ControllerCmd { Default, Test } #[derive(Debug)] -enum ControllerResponse { +pub(super) enum ControllerResponse { Empty, QueueMailMan(MailMan), } #[derive(Debug)] -pub enum DatabaseCmd { +pub(super) enum DatabaseCmd { Default, Test, SaveLibrary, @@ -49,11 +51,10 @@ pub enum DatabaseCmd { QueryUuid(Uuid), QueryUuids(Vec), ReadFolder(String), - } #[derive(Debug)] -enum DatabaseResponse { +pub(super) enum DatabaseResponse { Empty, Song(Song), Songs(Vec), @@ -61,7 +62,7 @@ enum DatabaseResponse { } #[derive(Debug)] -enum QueueCmd { +pub(super) enum QueueCmd { Default, Test, Play, @@ -73,25 +74,26 @@ enum QueueCmd { } #[derive(Debug)] -enum QueueResponse { +pub(super) enum QueueResponse { Default, Test, Index(i32), + Uuid(Uuid), } #[derive(Debug)] -struct MailMan { +pub(super) struct MailMan { pub tx: Sender, rx: Receiver } -impl MailMan { +impl MailMan { pub fn new() -> Self { let (tx, rx) = unbounded::(); MailMan { tx, rx } } } -impl MailMan { +impl MailMan { pub fn double() -> (MailMan, MailMan) { let (tx, rx) = unbounded::(); let (tx1, rx1) = unbounded::(); @@ -108,14 +110,16 @@ impl MailMan { } pub fn recv(&self) -> Result> { - let u = self.rx.recv().unwrap(); + let u = self.rx.recv()?; Ok(u) } } #[allow(unused_variables)] impl Controller { - pub fn start(config_path: String) -> Result> { + pub fn start

(config_path: P) -> Result> + where std::path::PathBuf: std::convert::From

+ { let config_path = PathBuf::from(config_path); let config = Config::read_file(config_path)?; let uuid = config.libraries.get_default()?.uuid; @@ -185,7 +189,6 @@ impl Controller { }); - Ok( Controller { // queues: Vec::new(), @@ -197,7 +200,7 @@ impl Controller { ) } - fn lib_get_songs(&self) -> Vec { + pub fn lib_get_songs(&self) -> Vec { self.db_mail.send(DatabaseCmd::GetSongs); match self.db_mail.recv().unwrap() { DatabaseResponse::Songs(songs) => songs, @@ -205,7 +208,7 @@ impl Controller { } } - fn lib_scan_folder(&self, folder: String) -> Result<(), Box> { + pub fn lib_scan_folder(&self, folder: String) -> Result<(), Box> { let mail = &self.db_mail; mail.send(DatabaseCmd::ReadFolder(folder))?; dbg!(mail.recv()?); @@ -259,14 +262,14 @@ impl Controller { Ok(self.queue_mail.len() - 1) } - fn q_play(&self, index: usize) -> Result<(), Box> { + pub fn q_play(&self, index: usize) -> Result<(), Box> { let mail = &self.queue_mail[index]; mail.send(QueueCmd::Play)?; dbg!(mail.recv()?); Ok(()) } - fn q_pause(&self, index: usize) -> Result<(), Box> { + pub fn q_pause(&self, index: usize) -> Result<(), Box> { let mail = &self.queue_mail[index]; mail.send(QueueCmd::Pause)?; dbg!(mail.recv()?); @@ -286,7 +289,7 @@ impl Controller { // Ok(()) // } - fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box> { + pub fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box> { let mail = &self.queue_mail[index]; mail.send(QueueCmd::Enqueue(uri))?; // dbg!(mail.recv()?); diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 6180e9d..483010f 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -89,10 +89,10 @@ pub enum PlayerLocation { #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub struct QueueItem<'a> { - item: QueueItemType<'a>, - state: QueueState, - source: PlayerLocation, - by_human: bool + pub(super) item: QueueItemType<'a>, + pub(super) state: QueueState, + pub(super) source: PlayerLocation, + pub(super) by_human: bool } impl QueueItem<'_> { fn new() -> Self { diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs index bd40b6a..63876fb 100644 --- a/src/music_storage/playlist.rs +++ b/src/music_storage/playlist.rs @@ -1,18 +1,21 @@ use std::{fs::File, io::Read, path:: PathBuf, sync::{Arc, RwLock}}; use std::error::Error; -use chrono::Duration; +use std::time::Duration; +use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI}; use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2}; -#[derive(Debug, Clone)] +use rayon::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum SortOrder { Manual, - Tag(Tag) + Tag(Vec) } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Playlist { uuid: Uuid, title: String, @@ -29,9 +32,24 @@ impl Playlist { pub fn play_count(&self) -> i32 { self.play_count } - pub fn play_time(&self) -> chrono::Duration { + pub fn play_time(&self) -> Duration { self.play_time } + + fn title(&self) -> &String { + &self.title + } + + fn cover(&self) -> Option<&AlbumArt> { + match &self.cover { + Some(e) => Some(e), + None => None, + } + } + + fn tracks(&self) -> Vec { + self.tracks.to_owned() + } pub fn set_tracks(&mut self, tracks: Vec) { self.tracks = tracks; } @@ -75,6 +93,15 @@ impl Playlist { false } + pub fn to_file(&self, path: &str) -> Result<(), Box> { + super::utils::write_file(self, PathBuf::from(path))?; + Ok(()) + } + + pub fn from_file(path: &str) -> Result> { + super::utils::read_file(PathBuf::from(path)) + } + pub fn to_m3u8(&mut self, lib: Arc>, location: &str) -> Result<(), Box> { let lib = lib.read().unwrap(); let seg = self.tracks @@ -174,22 +201,72 @@ impl Playlist { } } } - fn title(&self) -> &String { - &self.title - } - fn cover(&self) -> Option<&AlbumArt> { - match &self.cover { - Some(e) => Some(e), - None => None, + + + pub fn out_tracks(&self, lib: Arc>) -> (Vec, Vec<&Uuid>) { + let lib = lib.read().unwrap(); + let mut songs = vec![]; + let mut invalid_uuids = vec![]; + + for uuid in &self.tracks { + if let Some((track, _)) = lib.query_uuid(uuid) { + songs.push(track.to_owned()); + }else { + invalid_uuids.push(uuid); + } } - } - fn tracks(&self) -> Vec { - self.tracks.to_owned() + + if let SortOrder::Tag(sort_by) = &self.sort_order { + println!("sorting by: {:?}", sort_by); + + songs.par_sort_by(|a, b| { + for (i, sort_option) in sort_by.iter().enumerate() { + dbg!(&i); + let tag_a = match sort_option { + Tag::Field(field_selection) => match a.get_field(field_selection.as_str()) { + Some(field_value) => field_value.to_string(), + None => continue, + }, + _ => match a.get_tag(sort_option) { + Some(tag_value) => tag_value.to_owned(), + None => continue, + }, + }; + + let tag_b = match sort_option { + Tag::Field(field_selection) => match b.get_field(field_selection) { + Some(field_value) => field_value.to_string(), + None => continue, + }, + _ => match b.get_tag(sort_option) { + Some(tag_value) => tag_value.to_owned(), + None => continue, + }, + }; + dbg!(&i); + + if let (Ok(num_a), Ok(num_b)) = (tag_a.parse::(), tag_b.parse::()) { + // If parsing succeeds, compare as numbers + return dbg!(num_a.cmp(&num_b)); + } else { + // If parsing fails, compare as strings + return dbg!(tag_a.cmp(&tag_b)); + } + } + + // If all tags are equal, sort by Track number + let path_a = PathBuf::from(a.get_field("location").unwrap().to_string()); + let path_b = PathBuf::from(b.get_field("location").unwrap().to_string()); + + path_a.file_name().cmp(&path_b.file_name()) + }) + } + + (songs, invalid_uuids) } } - impl Default for Playlist { fn default() -> Self { Playlist { @@ -199,7 +276,7 @@ impl Default for Playlist { tracks: Vec::default(), sort_order: SortOrder::Manual, play_count: 0, - play_time: Duration::zero(), + play_time: Duration::from_millis(0), } } } @@ -207,7 +284,7 @@ impl Default for Playlist { #[cfg(test)] mod test_super { use super::*; - use crate::config::config::tests::read_config_lib; + use crate::{config::config::tests::read_config_lib, music_storage::playlist}; #[test] fn list_to_m3u8() { @@ -219,11 +296,24 @@ mod test_super { _ = playlist.to_m3u8(Arc::new(RwLock::from(lib)), ".\\test-config\\playlists\\playlist.m3u8"); } - #[test] - fn m3u8_to_list() { + + fn m3u8_to_list() -> Playlist { let (_, lib) = read_config_lib(); let arc = Arc::new(RwLock::from(lib)); let playlist = Playlist::from_m3u8(".\\test-config\\playlists\\playlist.m3u8", arc).unwrap(); - dbg!(playlist); + + playlist.to_file(".\\test-config\\playlists\\playlist"); + dbg!(playlist) + } + + #[test] + fn out_queue_sort() { + let (_, lib) = read_config_lib(); + let mut list = m3u8_to_list(); + list.sort_order = SortOrder::Tag(vec![Tag::Album]); + + let songs = &list.out_tracks(Arc::new(RwLock::from(lib))); + + dbg!(songs); } } From 94e6c2521901baac363a2776f8055c3e3bc562d4 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Tue, 7 May 2024 18:28:03 -0400 Subject: [PATCH 26/28] made a few minor changes --- Cargo.toml | 1 + src/music_controller/connections.rs | 8 ++++++++ src/music_controller/queue.rs | 2 +- src/music_storage/utils.rs | 6 +++--- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 49ac4e7..e3f236f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,4 @@ deunicode = "1.4.2" opener = { version = "0.7.0", features = ["reveal"] } tempfile = "3.10.1" listenbrainz = "0.7.0" +discord-rpc-client = "0.4.0" diff --git a/src/music_controller/connections.rs b/src/music_controller/connections.rs index dec723c..bf5e688 100644 --- a/src/music_controller/connections.rs +++ b/src/music_controller/connections.rs @@ -3,6 +3,7 @@ use std::{ error::Error, }; +use discord_rpc_client::Client; use listenbrainz::ListenBrainz; use uuid::Uuid; @@ -65,6 +66,13 @@ impl Controller { client.listen(artist, track, release)?; Ok(()) } + + pub fn discord_song_change(client: &mut Client,song: Song) { + client.set_activity(|a| { + a.state(format!("Listening to {}", song.get_tag(&Tag::Title).unwrap())) + .into() + }); + } } #[cfg(test)] diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 483010f..0559696 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -66,7 +66,7 @@ impl QueueItemType<'_> { if !shuffled { Some(album.track(*disc as usize, *index as usize).unwrap().location.clone()) }else { - todo!() + todo!() //what to do for non shuffled album } }, ExternalSong(uri) => { Some(uri.clone()) }, diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs index 1f68a8c..2f04d4d 100644 --- a/src/music_storage/utils.rs +++ b/src/music_storage/utils.rs @@ -25,12 +25,12 @@ pub(super) fn normalize(input_string: &str) -> String { /// Write any data structure which implements [serde::Serialize] /// out to a [bincode] encoded file compressed using [snap] -pub(super) fn write_file( +pub(super) fn write_file+std::convert::AsRef+Clone>( library: T, - path: PathBuf, + path: U, ) -> Result<(), Box> { // Create a temporary name for writing out - let mut writer_name = path.clone(); + let mut writer_name = PathBuf::from(&path); writer_name.set_extension("tmp"); // Create a new BufWriter on the file and a snap frame encoder From 56040bfd28fcff01cfdb8a1f4d0a076d5af22da6 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sun, 19 May 2024 18:48:29 -0400 Subject: [PATCH 27/28] Updated Controller and added Player Trait --- src/lib.rs | 7 +- src/music_controller/connections.rs | 158 +++++----- src/music_controller/controller.rs | 281 +++--------------- src/music_controller/queue.rs | 224 ++------------ .../gstreamer.rs} | 44 +-- src/music_player/kira.rs | 0 src/music_player/player.rs | 57 ++++ 7 files changed, 226 insertions(+), 545 deletions(-) rename src/{music_player.rs => music_player/gstreamer.rs} (94%) create mode 100644 src/music_player/kira.rs create mode 100644 src/music_player/player.rs diff --git a/src/lib.rs b/src/lib.rs index 387a488..9f6bb0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,13 +8,16 @@ pub mod music_storage { pub mod db_reader; } -pub mod music_controller{ +pub mod music_controller { pub mod controller; pub mod connections; pub mod queue; } -pub mod music_player; +pub mod music_player { + pub mod gstreamer; + pub mod player; +} #[allow(clippy::module_inception)] pub mod config { pub mod config; diff --git a/src/music_controller/connections.rs b/src/music_controller/connections.rs index bf5e688..b2ceb85 100644 --- a/src/music_controller/connections.rs +++ b/src/music_controller/connections.rs @@ -1,103 +1,103 @@ -use std::{ - sync::{Arc, RwLock}, - error::Error, -}; +// use std::{ +// sync::{Arc, RwLock}, +// error::Error, +// }; -use discord_rpc_client::Client; -use listenbrainz::ListenBrainz; -use uuid::Uuid; +// use discord_rpc_client::Client; +// use listenbrainz::ListenBrainz; +// use uuid::Uuid; -use crate::{ - config::config::Config, music_controller::controller::{Controller, QueueCmd, QueueResponse}, music_storage::library::{MusicLibrary, Song, Tag} -}; +// use crate::{ +// config::config::Config, music_controller::controller::{Controller, QueueCmd, QueueResponse}, music_storage::library::{MusicLibrary, Song, Tag} +// }; -use super::controller::DatabaseResponse; +// use super::controller::DatabaseResponse; -impl Controller { - pub fn listenbrainz_authenticate(&mut self) -> Result> { - let config = &self.config.read().unwrap(); - let mut client = ListenBrainz::new(); +// impl Controller { +// pub fn listenbrainz_authenticate(&mut self) -> Result> { +// let config = &self.config.read().unwrap(); +// let mut client = ListenBrainz::new(); - let lbz_token = match &config.connections.listenbrainz_token { - Some(token) => token, - None => todo!("No ListenBrainz token in config") - }; +// let lbz_token = match &config.connections.listenbrainz_token { +// Some(token) => token, +// None => todo!("No ListenBrainz token in config") +// }; - if !client.is_authenticated() { - client.authenticate(lbz_token)?; - } +// if !client.is_authenticated() { +// client.authenticate(lbz_token)?; +// } - Ok(client) - } - pub fn lbz_scrobble(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box> { - let config = &self.config.read().unwrap(); +// Ok(client) +// } +// pub fn lbz_scrobble(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box> { +// let config = &self.config.read().unwrap(); - &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid)); - let res = &self.db_mail.recv()?; - let song = match res { - DatabaseResponse::Song(song) => song, - _ => todo!() - }; - let unknown = &"unknown".to_string(); - let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown); - let track = song.get_tag(&Tag::Title).unwrap_or(unknown); - let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str()); +// &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid)); +// let res = &self.db_mail.recv()?; +// let song = match res { +// DatabaseResponse::Song(song) => song, +// _ => todo!() +// }; +// let unknown = &"unknown".to_string(); +// let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown); +// let track = song.get_tag(&Tag::Title).unwrap_or(unknown); +// let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str()); - client.listen(artist, track, release)?; - Ok(()) - } +// client.listen(artist, track, release)?; +// Ok(()) +// } - pub fn lbz_now_playing(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box> { - let config = &self.config.read().unwrap(); +// pub fn lbz_now_playing(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box> { +// let config = &self.config.read().unwrap(); - &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid)); - let res = &self.db_mail.recv()?; - let song = match res { - DatabaseResponse::Song(song) => song, - _ => todo!() - }; - let unknown = &"unknown".to_string(); - let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown); - let track = song.get_tag(&Tag::Title).unwrap_or(unknown); - let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str()); +// &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid)); +// let res = &self.db_mail.recv()?; +// let song = match res { +// DatabaseResponse::Song(song) => song, +// _ => todo!() +// }; +// let unknown = &"unknown".to_string(); +// let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown); +// let track = song.get_tag(&Tag::Title).unwrap_or(unknown); +// let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str()); - client.listen(artist, track, release)?; - Ok(()) - } +// client.listen(artist, track, release)?; +// Ok(()) +// } - pub fn discord_song_change(client: &mut Client,song: Song) { - client.set_activity(|a| { - a.state(format!("Listening to {}", song.get_tag(&Tag::Title).unwrap())) - .into() - }); - } -} +// pub fn discord_song_change(client: &mut Client,song: Song) { +// client.set_activity(|a| { +// a.state(format!("Listening to {}", song.get_tag(&Tag::Title).unwrap())) +// .into() +// }); +// } +// } -#[cfg(test)] -mod test_super { - use std::{thread::sleep, time::Duration}; +// #[cfg(test)] +// mod test_super { +// use std::{thread::sleep, time::Duration}; - use super::*; - use crate::config::config::tests::read_config_lib; +// use super::*; +// use crate::config::config::tests::read_config_lib; - #[test] - fn listenbrainz() { - let mut c = Controller::start(".\\test-config\\config_test.json").unwrap(); +// #[test] +// fn listenbrainz() { +// let mut c = Controller::start(".\\test-config\\config_test.json").unwrap(); - let client = c.listenbrainz_authenticate().unwrap(); +// let client = c.listenbrainz_authenticate().unwrap(); - c.q_new().unwrap(); - c.queue_mail[0].send(QueueCmd::SetVolume(0.04)).unwrap(); +// c.q_new().unwrap(); +// c.queue_mail[0].send(QueueCmd::SetVolume(0.04)).unwrap(); - let songs = c.lib_get_songs(); +// let songs = c.lib_get_songs(); - c.q_enqueue(0, songs[1].location.to_owned()).unwrap(); - c.q_play(0).unwrap(); +// c.q_enqueue(0, songs[1].location.to_owned()).unwrap(); +// c.q_play(0).unwrap(); - sleep(Duration::from_secs(100)); - c.lbz_scrobble(client, songs[1].uuid).unwrap(); - } -} +// sleep(Duration::from_secs(100)); +// c.lbz_scrobble(client, songs[1].uuid).unwrap(); +// } +// } diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index d13a62f..8efb630 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -13,7 +13,8 @@ use std::error::Error; use crossbeam_channel::unbounded; use uuid::Uuid; -use crate::music_controller::queue::{QueueItem, QueueItemType}; +use crate::music_controller::queue::QueueItem; +use crate::music_player::gstreamer::GStreamer; use crate::music_storage::library::{Tag, URI}; use crate::{ music_storage::library::{MusicLibrary, Song}, @@ -22,63 +23,10 @@ use crate::{ }; pub struct Controller { - // queues: Vec, + pub queue: Queue, pub config: Arc>, - // library: MusicLibrary, - pub(super) controller_mail: MailMan, - pub(super) db_mail: MailMan, - pub(super) queue_mail: Vec>, -} -#[derive(Debug)] -pub(super) enum ControllerCmd { - Default, - Test -} - -#[derive(Debug)] -pub(super) enum ControllerResponse { - Empty, - QueueMailMan(MailMan), - -} - -#[derive(Debug)] -pub(super) enum DatabaseCmd { - Default, - Test, - SaveLibrary, - GetSongs, - QueryUuid(Uuid), - QueryUuids(Vec), - ReadFolder(String), -} - -#[derive(Debug)] -pub(super) enum DatabaseResponse { - Empty, - Song(Song), - Songs(Vec), - Library(MusicLibrary), -} - -#[derive(Debug)] -pub(super) enum QueueCmd { - Default, - Test, - Play, - Pause, - // SetSongs(Vec>), - // SetLocation(URI), - Enqueue(URI), - SetVolume(f64), -} - -#[derive(Debug)] -pub(super) enum QueueResponse { - Default, - Test, - Index(i32), - Uuid(Uuid), + pub library: MusicLibrary, + player_mail: MailMan } #[derive(Debug)] @@ -115,227 +63,72 @@ impl MailMan { } } +enum PlayerCmd { + Test(URI) +} + +enum PlayerRes { + Test +} + #[allow(unused_variables)] impl Controller { pub fn start

(config_path: P) -> Result> where std::path::PathBuf: std::convert::From

{ let config_path = PathBuf::from(config_path); + let config = Config::read_file(config_path)?; let uuid = config.libraries.get_default()?.uuid; let config_ = Arc::new(RwLock::from(config)); - let mut lib = MusicLibrary::init(config_.clone(), uuid)?; + let library = MusicLibrary::init(config_.clone(), uuid)?; - let config = config_.clone(); - let (out_thread_controller, in_thread) = MailMan::double(); - let monitor_thread = spawn(move || { - use ControllerCmd::*; - loop { - let command = in_thread.recv().unwrap(); + let (player_mail, in_thread) = MailMan::::double(); - match command { - Default => (), - Test => { - in_thread.send(ControllerResponse::Empty).unwrap(); - }, - } - } - }); + spawn(move || { + let mut player = GStreamer::new().unwrap(); - let config = config_.clone(); - let (out_thread_db, in_thread) = MailMan::double(); - let db_monitor = spawn(move || { - use DatabaseCmd::*; - loop { - let command = in_thread.recv().unwrap(); - - match command { - Default => {}, - Test => { - in_thread.send(DatabaseResponse::Empty).unwrap(); - }, - GetSongs => { - let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect(); - in_thread.send(DatabaseResponse::Songs(songs)).unwrap(); - }, - SaveLibrary => { - //TODO: make this send lib ref to the function to save instead - lib.save(config.read().unwrap().to_owned()).unwrap(); - }, - QueryUuid(uuid) => { - match lib.query_uuid(&uuid) { - Some(song) => in_thread.send(DatabaseResponse::Song(song.0.clone())).unwrap(), - None => in_thread.send(DatabaseResponse::Empty).unwrap(), - } - }, - QueryUuids(uuids) => { - let mut vec = Vec::new(); - for uuid in uuids { - match lib.query_uuid(&uuid) { - Some(song) => vec.push(song.0.clone()), - None => unimplemented!() - } - } - in_thread.send(DatabaseResponse::Songs(vec)).unwrap(); - }, - ReadFolder(folder) => { - lib.scan_folder(&folder).unwrap(); - in_thread.send(DatabaseResponse::Empty).unwrap(); + while true { + match in_thread.recv().unwrap() { + PlayerCmd::Test(uri) => { + &player.set_volume(0.04); + _ = &player.enqueue_next(&uri).unwrap(); + _ = &player.play(); + in_thread.send(PlayerRes::Test).unwrap(); } - } } + }); Ok( Controller { - // queues: Vec::new(), + queue: Queue::new(), config: config_.clone(), - controller_mail: out_thread_controller, - db_mail: out_thread_db, - queue_mail: Vec::new(), + library, + player_mail } ) } - pub fn lib_get_songs(&self) -> Vec { - self.db_mail.send(DatabaseCmd::GetSongs); - match self.db_mail.recv().unwrap() { - DatabaseResponse::Songs(songs) => songs, - _ => Vec::new() - } + pub fn q_add(&self, item: Uuid, source:super::queue::PlayerLocation , by_human: bool) { + self.queue.add_item(item, source, by_human) } - pub fn lib_scan_folder(&self, folder: String) -> Result<(), Box> { - let mail = &self.db_mail; - mail.send(DatabaseCmd::ReadFolder(folder))?; - dbg!(mail.recv()?); - Ok(()) - } - - pub fn lib_save(&self) -> Result<(), Box> { - self.db_mail.send(DatabaseCmd::SaveLibrary); - Ok(()) - } - - pub fn q_new(&mut self) -> Result> { - let (out_thread_queue, in_thread) = MailMan::::double(); - let queues_monitor = spawn(move || { - use QueueCmd::*; - let mut queue = Queue::new().unwrap(); - loop { - let command = in_thread.recv().unwrap(); - match command { - Default => {}, - Test => { in_thread.send(QueueResponse::Test).unwrap() }, - Play => { - match queue.player.play() { - Ok(_) => in_thread.send(QueueResponse::Default).unwrap(), - Err(_) => todo!() - }; - - }, - Pause => { - match queue.player.pause() { - Ok(_) => in_thread.send(QueueResponse::Default).unwrap(), - Err(_) => todo!() - } - }, - // SetSongs(songs) => { - // queue.set_tracks(songs); - // in_thread.send(QueueResponse::Default).unwrap(); - // }, - Enqueue(uri) => { - queue.player.enqueue_next(&uri).unwrap(); - - // in_thread.send(QueueResponse::Default).unwrap(); - }, - SetVolume(vol) => { - queue.player.set_volume(vol); - } - } - } - }); - self.queue_mail.push(out_thread_queue); - Ok(self.queue_mail.len() - 1) - } - - pub fn q_play(&self, index: usize) -> Result<(), Box> { - let mail = &self.queue_mail[index]; - mail.send(QueueCmd::Play)?; - dbg!(mail.recv()?); - Ok(()) - } - - pub fn q_pause(&self, index: usize) -> Result<(), Box> { - let mail = &self.queue_mail[index]; - mail.send(QueueCmd::Pause)?; - dbg!(mail.recv()?); - Ok(()) - } - - pub fn q_set_volume(&self, index: usize, volume: f64) -> Result<(), Box> { - let mail = &self.queue_mail[index]; - mail.send(QueueCmd::SetVolume(volume))?; - Ok(()) - } - - // fn q_set_songs(&self, index: usize, songs: Vec>) -> Result<(), Box> { - // let mail = &self.queue_mail[index]; - // mail.send(QueueCmd::SetSongs(songs))?; - // dbg!(mail.recv()?); - // Ok(()) - // } - - pub fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box> { - let mail = &self.queue_mail[index]; - mail.send(QueueCmd::Enqueue(uri))?; - // dbg!(mail.recv()?); - Ok(()) - } - - } #[cfg(test)] -mod tests { +mod test_super { use std::{thread::sleep, time::Duration}; - use super::Controller; - - #[test] - fn play_test() { - let mut a = match Controller::start("test-config/config_test.json".to_string()) { - Ok(c) => c, - Err(e) => panic!("{e}") - }; - sleep(Duration::from_millis(500)); - - let i = a.q_new().unwrap(); - a.q_set_volume(i, 0.04); - // a.new_queue(); - let songs = a.lib_get_songs(); - a.q_enqueue(i, songs[2].location.clone()); - // a.enqueue(1, songs[2].location.clone()); - a.q_play(i).unwrap(); - // a.play(1).unwrap(); - - sleep(Duration::from_secs(10)); - a.q_pause(i); - sleep(Duration::from_secs(10)); - a.q_play(i); - sleep(Duration::from_secs(1000)); - } + use super::*; #[test] fn test_() { - let a = match Controller::start("test-config/config_test.json".to_string()) { - Ok(c) => c, - Err(e) => panic!("{e}") - }; - a.lib_scan_folder("F:/Music/Mp3".to_string()); - a.lib_save(); + let c = Controller::start("F:\\Dangoware\\Dango Music Player\\dmp-core\\test-config\\config_test.json").unwrap(); + + sleep(Duration::from_secs(60)); } -} +} \ No newline at end of file diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs index 0559696..39736e6 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -1,8 +1,5 @@ use uuid::Uuid; -use crate::{ - music_player::{Player, PlayerError}, - music_storage::library::{Album, MusicLibrary, URI} -}; +use crate::music_storage::library::{MusicLibrary, Song, URI}; use std::{ error::Error, sync::{Arc, RwLock} @@ -27,54 +24,6 @@ pub enum QueueState { NoState, } -#[derive(Debug, Clone, PartialEq)] -#[non_exhaustive] -pub enum QueueItemType<'a> { - Song(Uuid), - ExternalSong(URI), - Album{ - album: Album<'a>, - shuffled: bool, - order: Option>, - // disc #, track # - current: (i32, i32) - }, - Playlist { - uuid: Uuid, - shuffled: bool, - order: Option>, - current: Uuid - }, - None, - Test -} - -impl QueueItemType<'_> { - fn get_uri(&self, lib: Arc>) -> Option { - use QueueItemType::*; - - let lib = lib.read().unwrap(); - match self { - Song(uuid) => { - if let Some((song, _)) = lib.query_uuid(uuid) { - Some(song.location.clone()) - }else { - Option::None - } - }, - Album{album, shuffled, current: (disc, index), ..} => { - if !shuffled { - Some(album.track(*disc as usize, *index as usize).unwrap().location.clone()) - }else { - todo!() //what to do for non shuffled album - } - }, - ExternalSong(uri) => { Some(uri.clone()) }, - _ => { Option::None } - } - } -} - // TODO: move this to a different location to be used elsewhere #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] @@ -88,16 +37,16 @@ pub enum PlayerLocation { #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] -pub struct QueueItem<'a> { - pub(super) item: QueueItemType<'a>, +pub struct QueueItem { + pub(super) item: Song, pub(super) state: QueueState, pub(super) source: PlayerLocation, pub(super) by_human: bool } -impl QueueItem<'_> { - fn new() -> Self { +impl From for QueueItem { + fn from(song: Song) -> Self { QueueItem { - item: QueueItemType::None, + item: song, state: QueueState::NoState, source: PlayerLocation::Library, by_human: false @@ -107,15 +56,14 @@ impl QueueItem<'_> { #[derive(Debug)] -pub struct Queue<'a> { - pub player: Player, - pub name: String, - pub items: Vec>, - pub played: Vec>, - pub loop_: bool +pub struct Queue { + pub items: Vec, + pub played: Vec, + pub loop_: bool, + pub shuffle: bool } -impl<'a> Queue<'a> { +impl Queue { fn has_addhere(&self) -> bool { for item in &self.items { if item.state == QueueState::AddHere { @@ -126,28 +74,26 @@ impl<'a> Queue<'a> { } fn dbg_items(&self) { - dbg!(self.items.iter().map(|item| item.item.clone() ).collect::>(), self.items.len()); + dbg!(self.items.iter().map(|item| item.item.clone() ).collect::>(), self.items.len()); } - pub fn new() -> Result { - Ok( - Queue { - player: Player::new()?, - name: String::new(), - items: Vec::new(), - played: Vec::new(), - loop_: false, - } - ) + pub fn new() -> Self { + //TODO: Make the queue take settings from config/state if applicable + Queue { + items: Vec::new(), + played: Vec::new(), + loop_: false, + shuffle: false, + } } - pub fn set_items(&mut self, tracks: Vec>) { + pub fn set_items(&mut self, tracks: Vec) { let mut tracks = tracks; self.items.clear(); self.items.append(&mut tracks); } - pub fn add_item(&mut self, item: QueueItemType<'a>, source: PlayerLocation, by_human: bool) { + pub fn add_item(&mut self, item: Song, source: PlayerLocation, by_human: bool) { let mut i: usize = 0; self.items = self.items.iter().enumerate().map(|(j, item_)| { @@ -168,7 +114,7 @@ impl<'a> Queue<'a> { }); } - pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: PlayerLocation) { + pub fn add_item_next(&mut self, item: Song, source: PlayerLocation) { use QueueState::*; let empty = self.items.is_empty(); @@ -176,14 +122,14 @@ impl<'a> Queue<'a> { (if empty { 0 } else { 1 }), QueueItem { item, - state: if (self.items.get(1).is_none() || (!self.has_addhere() && self.items.get(1).is_some()) || empty) { AddHere } else { NoState }, + state: if (self.items.get(1).is_none() || !self.has_addhere() && self.items.get(1).is_some()) || empty { AddHere } else { NoState }, source, by_human: true } ) } - pub fn add_multi(&mut self, items: Vec, source: PlayerLocation, by_human: bool) { + pub fn add_multi(&mut self, items: Vec, source: PlayerLocation, by_human: bool) { } @@ -239,14 +185,10 @@ impl<'a> Queue<'a> { use QueueState::*; let empty = self.items.is_empty(); - let nothing_error = Err(QueueError::EmptyQueue); - let index = if !empty { index } else { return nothing_error; }; + + let index = if !empty { index } else { return Err(QueueError::EmptyQueue); }; if !empty && index < self.items.len() { - let position = self.player.position(); - if position.is_some_and(|dur| !dur.is_zero() ) { - self.played.push(self.items[0].clone()); - } let to_item = self.items[index].clone(); @@ -263,13 +205,13 @@ impl<'a> Queue<'a> { } // dbg!(&to_item.item, &self.items[ind].item); }else if empty { - return nothing_error; + return Err(QueueError::EmptyQueue); }else { break; } } }else { - return Err(QueueError::EmptyQueue.into()); + return Err(QueueError::EmptyQueue); } Ok(()) } @@ -287,9 +229,7 @@ impl<'a> Queue<'a> { } #[allow(clippy::should_implement_trait)] - pub fn next(&mut self, lib: Arc>) -> Result> { - - + pub fn next(&mut self) -> Result<&QueueItem, Box> { if self.items.is_empty() { if self.loop_ { @@ -300,119 +240,21 @@ impl<'a> Queue<'a> { } // TODO: add an algorithm to detect if the song should be skipped let item = self.items[0].clone(); - let uri: URI = match &self.items[1].item { - QueueItemType::Song(uuid) => { - // TODO: Refactor later for multiple URIs - match &lib.read().unwrap().query_uuid(uuid) { - Some(song) => song.0.location.clone(), - None => return Err("Uuid does not exist!".into()), - } - }, - QueueItemType::Album { album, current, ..} => { - let (disc, track) = (current.0 as usize, current.1 as usize); - match album.track(disc, track) { - Some(track) => track.location.clone(), - None => return Err(format!("Track in Album {} at disc {} track {} does not exist!", album.title(), disc, track).into()) - } - }, - QueueItemType::Playlist { current, .. } => { - // TODO: Refactor later for multiple URIs - match &lib.read().unwrap().query_uuid(current) { - Some(song) => song.0.location.clone(), - None => return Err("Uuid does not exist!".into()), - } - }, - _ => todo!() - }; - if !self.player.is_paused() { - self.player.enqueue_next(&uri)?; - self.player.play()? - } + if self.items[0].state == QueueState::AddHere || !self.has_addhere() { self.items[1].state = QueueState::AddHere; } self.played.push(item); self.items.remove(0); - Ok(todo!()) + Ok(&self.items[1]) } pub fn prev() {} - pub fn enqueue_item(&mut self, item: QueueItem, lib: Arc>) -> Result<(), Box> { - if let Some(uri) = item.item.get_uri(lib) { - self.player.enqueue_next(&uri)?; - }else { - return Err("this item does not exist!".into()); - } - Ok(()) - } pub fn check_played(&mut self) { while self.played.len() > 50 { self.played.remove(0); } } } - -pub struct OutQueue { - -} - -pub enum OutQueueItem { - -} - - -#[test] -fn item_add_test() { - let mut q = Queue::new().unwrap(); - - for _ in 0..5 { - // dbg!("tick!"); - q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true); - // dbg!(&q.items, &q.items.len()); - } - - for _ in 0..1 { - q.remove_item(0).inspect_err(|e| println!("{e:?}")); - } - for _ in 0..2 { - q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: PlayerLocation::Library, by_human: false }); - } - dbg!(5); - - q.add_item_next(QueueItemType::Test, PlayerLocation::Test); - dbg!(6); - - dbg!(&q.items, &q.items.len()); -} - -#[test] -fn test_() { - let mut q = Queue::new().unwrap(); - for _ in 0..400 { - q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::NoState, source: PlayerLocation::File, by_human: false }); - } - for _ in 0..50000 { - q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true); - } - // q.add_item_next(QueueItemType::Test, PlayerLocation::File); - - // dbg!(&q.items, &q.items.len()); - -} - -#[test] -fn move_test() { - let mut q = Queue::new().unwrap(); - - for _ in 0..5 { - q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true); - } - // q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap(); - dbg!(&q.items, &q.items.len()); - - q.move_to(3).inspect_err(|e| {dbg!(e);}); - dbg!(&q.items, &q.items.len()); - // q.dbg_items(); -} diff --git a/src/music_player.rs b/src/music_player/gstreamer.rs similarity index 94% rename from src/music_player.rs rename to src/music_player/gstreamer.rs index e310403..91abfde 100644 --- a/src/music_player.rs +++ b/src/music_player/gstreamer.rs @@ -15,8 +15,10 @@ use gstreamer::prelude::*; use chrono::Duration; use thiserror::Error; +use super::player::{Player, PlayerError}; + #[derive(Debug)] -pub enum PlayerCmd { +pub enum GstCmd { Play, Pause, Eos, @@ -24,7 +26,7 @@ pub enum PlayerCmd { } #[derive(Debug, PartialEq, Eq)] -pub enum PlayerState { +pub enum GstState { Playing, Paused, Ready, @@ -33,7 +35,7 @@ pub enum PlayerState { VoidPending, } -impl From for PlayerState { +impl From for GstState { fn from(value: gst::State) -> Self { match value { gst::State::VoidPending => Self::VoidPending, @@ -45,7 +47,7 @@ impl From for PlayerState { } } -impl TryInto for PlayerState { +impl TryInto for GstState { fn try_into(self) -> Result> { match self { Self::VoidPending => Ok(gst::State::VoidPending), @@ -60,24 +62,6 @@ impl TryInto for PlayerState { type Error = Box; } -#[derive(Error, Debug)] -pub enum PlayerError { - #[error("player initialization failed")] - Init(#[from] glib::Error), - #[error("element factory failed to create playbin3")] - Factory(#[from] glib::BoolError), - #[error("could not change playback state")] - StateChange(#[from] gst::StateChangeError), - #[error("the file or source is not found")] - NotFound, - #[error("failed to build gstreamer item")] - Build, - #[error("poison error")] - Poison, - #[error("general player error")] - General, -} - #[derive(Debug, PartialEq, Eq)] enum PlaybackStats { Idle, @@ -91,10 +75,10 @@ enum PlaybackStats { /// An instance of a music player with a GStreamer backend #[derive(Debug)] -pub struct Player { +pub struct GStreamer { source: Option, //pub message_tx: Sender, - pub message_rx: crossbeam::channel::Receiver, + pub message_rx: crossbeam::channel::Receiver, playback_tx: crossbeam::channel::Sender, playbin: Arc>, @@ -105,7 +89,7 @@ pub struct Player { position: Arc>>, } -impl Player { +impl GStreamer { pub fn new() -> Result { // Initialize GStreamer, maybe figure out how to nicely fail here gst::init()?; @@ -162,14 +146,14 @@ impl Player { // Check if the current playback position is close to the end let finish_point = end - Duration::milliseconds(250); if pos_temp.unwrap() >= end { - let _ = playback_tx.try_send(PlayerCmd::Eos); + let _ = playback_tx.try_send(GstCmd::Eos); playbin_arc .write() .unwrap() .set_state(gst::State::Ready) .expect("Unable to set the pipeline state"); } else if pos_temp.unwrap() >= finish_point { - let _ = playback_tx.try_send(PlayerCmd::AboutToFinish); + let _ = playback_tx.try_send(GstCmd::AboutToFinish); } // This has to be done AFTER the current time in the file @@ -468,7 +452,7 @@ impl Player { } /// Get the current state of the playback - pub fn state(&mut self) -> PlayerState { + pub fn state(&mut self) -> GstState { self.playbin().unwrap().current_state().into() /* match *self.buffer.read().unwrap() { @@ -498,7 +482,9 @@ impl Player { } } -impl Drop for Player { +// impl Player for GStreamer {} + +impl Drop for GStreamer { /// Cleans up the `GStreamer` pipeline and the monitoring /// thread when [Player] is dropped. fn drop(&mut self) { diff --git a/src/music_player/kira.rs b/src/music_player/kira.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/music_player/player.rs b/src/music_player/player.rs new file mode 100644 index 0000000..a30b2a6 --- /dev/null +++ b/src/music_player/player.rs @@ -0,0 +1,57 @@ +use chrono::Duration; +use thiserror::Error; +use gstreamer as gst; + +use crate::music_storage::library::URI; + + +#[derive(Error, Debug)] +pub enum PlayerError { + #[error("player initialization failed")] + Init(#[from] glib::Error), + #[error("element factory failed to create playbin3")] + Factory(#[from] glib::BoolError), + #[error("could not change playback state")] + StateChange(#[from] gst::StateChangeError), + #[error("the file or source is not found")] + NotFound, + #[error("failed to build gstreamer item")] + Build, + #[error("poison error")] + Poison, + #[error("general player error")] + General, +} + +pub trait Player { + fn source(&self) -> &Option; + + fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError>; + + fn set_volume(&mut self, volume: f64); + + fn volume(&mut self) -> f64; + + fn ready(&mut self) -> Result<(), PlayerError>; + + fn play(&mut self) -> Result<(), PlayerError>; + + fn resume(&mut self) -> Result<(), PlayerError>; + + fn pause(&mut self) -> Result<(), PlayerError>; + + fn stop(&mut self) -> Result<(), PlayerError>; + + fn is_paused(&mut self) -> bool; + + fn position(&mut self) -> Option; + + fn duration(&mut self) -> Option; + + fn raw_duration(&self) -> Option; + + fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError>; + + fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError>; + +} \ No newline at end of file From 9457c5c9965e44c256ea91f9182d99608dacf883 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sun, 19 May 2024 22:29:30 -0400 Subject: [PATCH 28/28] fixed a function and removed Album Art function --- src/music_controller/controller.rs | 3 +- src/music_storage/library.rs | 126 +++++++---------------------- 2 files changed, 33 insertions(+), 96 deletions(-) diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index 525fd9f..2ab8811 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -107,7 +107,8 @@ impl Controller { }) } - pub fn q_add(&self, item: Uuid, source: super::queue::PlayerLocation, by_human: bool) { + pub fn q_add(&mut self, item: &Uuid, source: super::queue::PlayerLocation, by_human: bool) { + let item = self.library.query_uuid(item).unwrap().0.to_owned(); self.queue.add_item(item, source, by_human) } } diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index a64101f..4818324 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -9,16 +9,15 @@ use std::error::Error; use std::io::Write; use std::ops::ControlFlow::{Break, Continue}; - // Files use file_format::{FileFormat, Kind}; use glib::filename_to_uri; use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt}; use rcue::parser::parse_from_file; -use uuid::Uuid; use std::fs::{self, File}; -use tempfile::TempDir; use std::path::{Path, PathBuf}; +use tempfile::TempDir; +use uuid::Uuid; use walkdir::WalkDir; // Time @@ -152,7 +151,7 @@ pub enum SongType { Main, Instrumental, Remix, - Custom(String) + Custom(String), } impl Default for SongType { @@ -182,10 +181,9 @@ pub struct Song { pub date_modified: Option>, pub album_art: Vec, pub tags: BTreeMap, - pub internal_tags: Vec + pub internal_tags: Vec, } - impl Song { /// Get a tag's value /// @@ -342,7 +340,6 @@ impl Song { for file in cue_data.files.iter() { let audio_location = &parent_dir.join(file.file.clone()); - if !audio_location.exists() { continue; } @@ -447,80 +444,12 @@ impl Song { date_modified: Some(chrono::offset::Utc::now()), tags, album_art, - internal_tags: Vec::new() + internal_tags: Vec::new(), }; tracks.push((new_song, audio_location.clone())); } } - Ok(tracks) - } - - /// Takes the AlbumArt[index] and opens it in the native file viewer - - pub fn open_album_art(&self, index: usize, temp_dir: &TempDir) -> Result<(), Box> { - use opener::open; - use urlencoding::decode; - - if index >= self.album_art.len() { - return Err("Index out of bounds".into()); - } - - let uri = match &self.album_art[index] { - AlbumArt::External(uri) => { - PathBuf::from(decode(match uri.as_uri().strip_prefix("file:///") { - Some(e) => e, - None => return Err("Invalid path?".into()) - })?.to_owned().to_string()) - }, - AlbumArt::Embedded(_) => { - let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed); - let blank_tag = &lofty::Tag::new(TagType::Id3v2); - let tagged_file: lofty::TaggedFile; - - // TODO: add support for other URI types... or don't - #[cfg(target_family = "windows")] - let uri = urlencoding::decode( - match self.primary_uri()?.0.as_uri().strip_prefix("file:///") { - Some(str) => str, - None => return Err("invalid path.. again?".into()) - })?.into_owned(); - - #[cfg(target_family = "unix")] - let uri = urlencoding::decode( - match self.primary_uri()?.as_uri().strip_prefix("file://") { - Some(str) => str, - None => return Err("invalid path.. again?".into()) - })?.into_owned(); - - let tag = match Probe::open(uri)?.options(normal_options).read() { - Ok(file) => { - tagged_file = file; - - match tagged_file.primary_tag() { - Some(primary_tag) => primary_tag, - - None => match tagged_file.first_tag() { - Some(first_tag) => first_tag, - None => blank_tag, - }, - } - } - - Err(_) => blank_tag, - }; - - let data = tag.pictures()[index].data(); - - let fmt = FileFormat::from_bytes(data); - let file_path = temp_dir.path().join(format!("{}_{index}.{}", self.uuid, fmt.extension())); - - File::create(&file_path)?.write_all(data)?; - - file_path - }, - }; - dbg!(open(dbg!(uri))?); - Ok(()) + Ok(tracks) } /// Returns a reference to the first valid URI in the song, and any invalid URIs that come before it, or errors if there are no valid URIs @@ -533,13 +462,20 @@ impl Song { if uri.exists()? { valid_uri = Some(uri); break; - }else { + } else { invalid_uris.push(uri); } } match valid_uri { - Some(uri) => Ok((uri, if !invalid_uris.is_empty() { Some(invalid_uris) } else { None } )), - None => Err("No valid URIs for this song".into()) + Some(uri) => Ok(( + uri, + if !invalid_uris.is_empty() { + Some(invalid_uris) + } else { + None + }, + )), + None => Err("No valid URIs for this song".into()), } } } @@ -610,7 +546,7 @@ impl URI { pub fn as_path(&self) -> Result<&PathBuf, Box> { if let Self::Local(path) = self { Ok(path) - }else { + } else { Err("This URI is not local!".into()) } } @@ -618,7 +554,7 @@ impl URI { pub fn exists(&self) -> Result { match self { URI::Local(loc) => loc.try_exists(), - URI::Cue {location, ..} => location.try_exists(), + URI::Cue { location, .. } => location.try_exists(), URI::Remote(_, _loc) => todo!(), } } @@ -703,7 +639,7 @@ pub struct MusicLibrary { pub uuid: Uuid, pub library: Vec, pub playlists: PlaylistFolder, - pub backup_songs: Vec // maybe move this to the config instead? + pub backup_songs: Vec, // maybe move this to the config instead? } impl MusicLibrary { @@ -823,7 +759,8 @@ impl MusicLibrary { fn query_path(&self, path: PathBuf) -> Option> { let result: Arc>> = Arc::new(Mutex::new(Vec::new())); self.library.par_iter().for_each(|track| { - if path == track.primary_uri().unwrap().0.path() { //TODO: make this also not unwrap + if path == track.primary_uri().unwrap().0.path() { + //TODO: make this also not unwrap result.clone().lock().unwrap().push(track); } }); @@ -835,10 +772,7 @@ impl MusicLibrary { } /// Finds all the audio files within a specified folder - pub fn scan_folder( - &mut self, - target_path: &str, - ) -> Result> { + pub fn scan_folder(&mut self, target_path: &str) -> Result> { let mut total = 0; let mut errors = 0; for target_file in WalkDir::new(target_path) @@ -901,7 +835,6 @@ impl MusicLibrary { } pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box> { - let new_song = Song::from_file(target_file)?; match self.add_song(new_song) { Ok(_) => (), @@ -917,14 +850,13 @@ impl MusicLibrary { let tracks = Song::from_cue(cuesheet)?; let mut tracks_added = tracks.len() as i32; - for (new_song, location) in tracks { // Try to remove the original audio file from the db if it exists if self.remove_uri(&URI::Local(location.clone())).is_ok() { tracks_added -= 1 } match self.add_song(new_song) { - Ok(_) => {}, + Ok(_) => {} Err(_error) => { //println!("{}", _error); continue; @@ -1194,7 +1126,12 @@ impl MusicLibrary { #[cfg(test)] mod test { - use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}, thread::sleep, time::{Duration, Instant}}; + use std::{ + path::{Path, PathBuf}, + sync::{Arc, RwLock}, + thread::sleep, + time::{Duration, Instant}, + }; use tempfile::TempDir; @@ -1202,7 +1139,6 @@ mod test { use super::Song; - #[test] fn get_art_test() { let s = Song::from_file(Path::new("")).unwrap(); @@ -1211,7 +1147,7 @@ mod test { let now = Instant::now(); _ = s.open_album_art(0, dir).inspect_err(|e| println!("{e:?}")); _ = s.open_album_art(1, dir).inspect_err(|e| println!("{e:?}")); - println!("{}ms", now.elapsed().as_millis() ); + println!("{}ms", now.elapsed().as_millis()); sleep(Duration::from_secs(20)); } @@ -1223,4 +1159,4 @@ mod test { let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap(); dbg!(a); } -} \ No newline at end of file +}