diff --git a/Cargo.toml b/Cargo.toml index e3f236f..08c25dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,15 @@ keywords = [] categories = [] [dependencies] -file-format = { version = "0.23.0", features = ["reader-asf", "reader-ebml", "reader-mp4", "reader-rm", "reader-txt", "reader-xml", "serde"] } +file-format = { version = "0.23.0", features = [ + "reader-asf", + "reader-ebml", + "reader-mp4", + "reader-rm", + "reader-txt", + "reader-xml", + "serde", +] } lofty = "0.18.2" serde = { version = "1.0.195", features = ["derive"] } walkdir = "2.4.0" @@ -39,3 +47,4 @@ opener = { version = "0.7.0", features = ["reveal"] } tempfile = "3.10.1" listenbrainz = "0.7.0" discord-rpc-client = "0.4.0" +nestify = "0.3.3" diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs index 8efb630..525fd9f 100644 --- a/src/music_controller/controller.rs +++ b/src/music_controller/controller.rs @@ -2,37 +2,37 @@ //! player. It manages queues, playback, library access, and //! other functions +use crossbeam_channel; +use crossbeam_channel::{Receiver, Sender}; +use listenbrainz::ListenBrainz; 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 std::error::Error; use uuid::Uuid; 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}, config::config::Config, music_controller::queue::Queue, + music_storage::library::{MusicLibrary, Song}, }; pub struct Controller { pub queue: Queue, pub config: Arc>, pub library: MusicLibrary, - player_mail: MailMan + player_mail: MailMan, } #[derive(Debug)] pub(super) struct MailMan { pub tx: Sender, - rx: Receiver + rx: Receiver, } impl MailMan { @@ -46,10 +46,7 @@ impl MailMan { let (tx, rx) = unbounded::(); let (tx1, rx1) = unbounded::(); - ( - MailMan { tx, rx: rx1 }, - MailMan { tx: tx1, rx } - ) + (MailMan { tx, rx: rx1 }, MailMan { tx: tx1, rx }) } pub fn send(&self, mail: T) -> Result<(), Box> { @@ -64,17 +61,18 @@ impl MailMan { } enum PlayerCmd { - Test(URI) + Test(URI), } enum PlayerRes { - Test + Test, } #[allow(unused_variables)] impl Controller { pub fn start

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

+ where + std::path::PathBuf: std::convert::From

, { let config_path = PathBuf::from(config_path); @@ -99,36 +97,43 @@ impl Controller { } } } - }); - - Ok( - Controller { - queue: Queue::new(), - config: config_.clone(), - library, - player_mail - } - ) + Ok(Controller { + queue: Queue::new(), + config: config_.clone(), + library, + player_mail, + }) } - pub fn q_add(&self, item: Uuid, source:super::queue::PlayerLocation , by_human: bool) { + pub fn q_add(&self, item: Uuid, source: super::queue::PlayerLocation, by_human: bool) { self.queue.add_item(item, source, by_human) } - } #[cfg(test)] mod test_super { use std::{thread::sleep, time::Duration}; - use super::*; + 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)); + } #[test] fn test_() { - let c = Controller::start("F:\\Dangoware\\Dango Music Player\\dmp-core\\test-config\\config_test.json").unwrap(); + 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 39736e6..380d157 100644 --- a/src/music_controller/queue.rs +++ b/src/music_controller/queue.rs @@ -1,9 +1,9 @@ -use uuid::Uuid; use crate::music_storage::library::{MusicLibrary, Song, URI}; use std::{ error::Error, - sync::{Arc, RwLock} + sync::{Arc, RwLock}, }; +use uuid::Uuid; use thiserror::Error; @@ -12,8 +12,7 @@ pub enum QueueError { #[error("Index out of bounds! Index {0} is over len {1}")] OutOfBounds(usize, usize), #[error("The Queue is empty!")] - EmptyQueue - + EmptyQueue, } #[derive(Debug, PartialEq, Clone, Copy)] @@ -32,7 +31,7 @@ pub enum PlayerLocation { Library, Playlist(Uuid), File, - Custom + Custom, } #[derive(Debug, Clone, PartialEq)] @@ -41,7 +40,7 @@ pub struct QueueItem { pub(super) item: Song, pub(super) state: QueueState, pub(super) source: PlayerLocation, - pub(super) by_human: bool + pub(super) by_human: bool, } impl From for QueueItem { fn from(song: Song) -> Self { @@ -49,41 +48,46 @@ impl From for QueueItem { item: song, state: QueueState::NoState, source: PlayerLocation::Library, - by_human: false + by_human: false, } } } - #[derive(Debug)] pub struct Queue { pub items: Vec, pub played: Vec, pub loop_: bool, - pub shuffle: bool + pub shuffle: bool, } impl Queue { fn has_addhere(&self) -> bool { for item in &self.items { if item.state == QueueState::AddHere { - return true + return true; } } false } 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() -> Self { //TODO: Make the queue take settings from config/state if applicable Queue { - items: Vec::new(), - played: Vec::new(), - loop_: false, - shuffle: false, + items: Vec::new(), + played: Vec::new(), + loop_: false, + shuffle: false, } } @@ -96,22 +100,30 @@ impl Queue { 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_)| { - let mut item_ = item_.to_owned(); - // get the index of the current AddHere item and give it to i - if item_.state == QueueState::AddHere { - i = j; - item_.state = QueueState::NoState; - } - item_ - }).collect::>(); + 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 == QueueState::AddHere { + i = j; + item_.state = QueueState::NoState; + } + item_ + }) + .collect::>(); - self.items.insert(i + if self.items.is_empty() { 0 } else { 1 }, QueueItem { - item, - state: QueueState::AddHere, - source, - by_human - }); + 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: Song, source: PlayerLocation) { @@ -122,19 +134,23 @@ impl Queue { (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 - } + 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) {} pub fn remove_item(&mut self, remove_index: usize) -> Result<(), QueueError> { - // dbg!(/*&remove_index, self.current_index(), &index,*/ &self.items[remove_index]); if remove_index < self.items.len() { @@ -145,7 +161,7 @@ impl Queue { self.items[remove_index].state = QueueState::NoState; self.items.remove(remove_index); Ok(()) - }else { + } else { Err(QueueError::EmptyQueue) } } @@ -160,11 +176,11 @@ impl Queue { if !empty && index < self.items.len() { let i = self.items[index].clone(); - self.items.retain(|item| *item == i ); + self.items.retain(|item| *item == i); self.items[0].state = AddHere; - }else if empty { + } else if empty { return Err("Queue is empty!".into()); - }else { + } else { return Err("index out of bounds!".into()); } Ok(()) @@ -179,17 +195,19 @@ impl Queue { self.played.clear(); } - // TODO: uh, fix this? fn move_to(&mut self, index: usize) -> Result<(), QueueError> { use QueueState::*; let empty = self.items.is_empty(); - let index = if !empty { index } else { return Err(QueueError::EmptyQueue); }; + let index = if !empty { + index + } else { + return Err(QueueError::EmptyQueue); + }; if !empty && index < self.items.len() { - let to_item = self.items[index].clone(); loop { @@ -201,16 +219,18 @@ impl Queue { self.items[1].state = AddHere; } if let Err(e) = self.remove_item(0) { - dbg!(&e); self.dbg_items(); return Err(e); + dbg!(&e); + self.dbg_items(); + return Err(e); } // dbg!(&to_item.item, &self.items[ind].item); - }else if empty { + } else if empty { return Err(QueueError::EmptyQueue); - }else { + } else { break; } } - }else { + } else { return Err(QueueError::EmptyQueue); } Ok(()) @@ -230,17 +250,15 @@ impl Queue { #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Result<&QueueItem, Box> { - if self.items.is_empty() { if self.loop_ { return Err(QueueError::EmptyQueue.into()); // TODO: add function to loop the queue - }else { + } else { return Err(QueueError::EmptyQueue.into()); } } // TODO: add an algorithm to detect if the song should be skipped let item = self.items[0].clone(); - if self.items[0].state == QueueState::AddHere || !self.has_addhere() { self.items[1].state = QueueState::AddHere; } 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 22452a9..a64101f 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(), } } @@ -737,8 +783,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(()) }); @@ -774,7 +823,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); } }); @@ -886,13 +935,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()) } _ => (), } @@ -915,17 +965,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); @@ -1143,10 +1194,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; @@ -1162,4 +1215,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 63876fb..e7ae61b 100644 --- a/src/music_storage/playlist.rs +++ b/src/music_storage/playlist.rs @@ -1,21 +1,52 @@ -use std::{fs::File, io::Read, path:: PathBuf, sync::{Arc, RwLock}}; use std::error::Error; +use std::{ + fs::File, + io::Read, + path::PathBuf, + sync::{Arc, RwLock}, +}; use std::time::Duration; + +// use chrono::Duration; +use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI}; use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2}; +use nestify::nest; use rayon::prelude::*; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SortOrder { Manual, - Tag(Vec) + Tag(Vec), } -#[derive(Debug, Clone, Serialize, Deserialize)] + +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, @@ -75,11 +106,16 @@ impl Playlist { // } // None // } - pub fn contains_value(&self, tag: &Tag, value: &String, lib: Arc>) -> bool { + 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 + None => return false, }; for item in items { @@ -102,23 +138,35 @@ impl Playlist { super::utils::read_file(PathBuf::from(path)) } - pub fn to_m3u8(&mut self, lib: Arc>, location: &str) -> Result<(), Box> { + pub fn to_m3u8( + &mut self, + lib: Arc>, + location: &str, + ) -> Result<(), Box> { let lib = lib.read().unwrap(); - let seg = self.tracks + 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 } + .filter_map(|uuid| { + // TODO: The Unwraps need to be handled here + if let Some((track, _)) = lib.query_uuid(uuid) { + if let URI::Local(_) = track.primary_uri().unwrap().0 { + Some(MediaSegment { + uri: track.primary_uri().unwrap().0.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 { @@ -142,7 +190,10 @@ impl Playlist { Ok(()) } - pub fn from_m3u8(path: &str, lib: Arc>) -> 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.into()), @@ -158,7 +209,9 @@ impl Playlist { }; match playlist { - List2::MasterPlaylist(_) => Err("This is a Master Playlist!\nPlase input a Media Playlist".into()), + 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 { @@ -167,7 +220,7 @@ impl Playlist { let uuid = if let Some((song, _)) = lib.query_uri(&URI::Local(path_.clone())) { song.uuid - }else { + } else { let song_ = Song::from_file(&path_)?; let uuid = song_.uuid.to_owned(); lib.add_song(song_)?; @@ -179,21 +232,23 @@ impl Playlist { #[cfg(target_family = "windows")] { - playlist.title = path.split("\\") - .last() - .unwrap_or_default() - .strip_suffix(".m3u8") - .unwrap_or_default() - .to_string(); + 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.title = path + .split("/") + .last() + .unwrap_or_default() + .strip_suffix(".m3u8") + .unwrap_or_default() + .to_string(); } playlist.set_tracks(uuids); @@ -202,7 +257,6 @@ impl Playlist { } } - pub fn out_tracks(&self, lib: Arc>) -> (Vec, Vec<&Uuid>) { let lib = lib.read().unwrap(); let mut songs = vec![]; @@ -211,7 +265,7 @@ impl Playlist { for uuid in &self.tracks { if let Some((track, _)) = lib.query_uuid(uuid) { songs.push(track.to_owned()); - }else { + } else { invalid_uuids.push(uuid); } } @@ -223,10 +277,12 @@ impl Playlist { 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, - }, + 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, @@ -266,7 +322,6 @@ impl Playlist { } } - impl Default for Playlist { fn default() -> Self { Playlist { @@ -276,7 +331,7 @@ impl Default for Playlist { tracks: Vec::default(), sort_order: SortOrder::Manual, play_count: 0, - play_time: Duration::from_millis(0), + play_time: Duration::from_secs(0), } } } @@ -290,17 +345,20 @@ mod test_super { fn list_to_m3u8() { let (_, lib) = read_config_lib(); let mut playlist = Playlist::new(); - let tracks = lib.library.iter().map(|track| track.uuid ).collect(); + 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"); + _ = playlist.to_m3u8( + Arc::new(RwLock::from(lib)), + ".\\test-config\\playlists\\playlist.m3u8", + ); } - 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(); + let playlist = + Playlist::from_m3u8(".\\test-config\\playlists\\playlist.m3u8", arc).unwrap(); playlist.to_file(".\\test-config\\playlists\\playlist"); dbg!(playlist)