diff --git a/src/config/config.rs b/src/config/config.rs index 283d9c6..e3f021f 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -137,7 +137,11 @@ pub enum ConfigError { #[error("No Library Found for {0}!")] NoConfigLibrary(Uuid), #[error("There is no Default Library for this Config")] - NoDefaultLibrary + NoDefaultLibrary, + //TODO: do something about playlists + #[error("Please provide a better m3u8 Playlist")] + BadPlaylist, + } diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs index bf7620e..6b216e3 100644 --- a/src/music_storage/library.rs +++ b/src/music_storage/library.rs @@ -175,6 +175,225 @@ impl Song { pub fn remove_tag(&mut self, target_key: &Tag) { self.tags.remove(target_key); } + + /// Creates a `Song` from a song file + pub fn from_file(target_file: &Path) -> Result> { + let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed); + + let blank_tag = &lofty::Tag::new(TagType::Id3v2); + let tagged_file: lofty::TaggedFile; + let mut duration = Duration::from_secs(0); + let tag = match Probe::open(target_file)?.options(normal_options).read() { + Ok(file) => { + tagged_file = file; + + duration = tagged_file.properties().duration(); + + // Ensure the tags exist, if not, insert blank data + 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 mut tags: BTreeMap = BTreeMap::new(); + for item in tag.items() { + let key = match item.key() { + ItemKey::TrackTitle => Tag::Title, + ItemKey::TrackNumber => Tag::Track, + ItemKey::TrackArtist => Tag::Artist, + ItemKey::AlbumArtist => Tag::AlbumArtist, + ItemKey::Genre => Tag::Genre, + ItemKey::Comment => Tag::Comment, + ItemKey::AlbumTitle => Tag::Album, + ItemKey::DiscNumber => Tag::Disk, + ItemKey::Unknown(unknown) + if unknown == "ACOUSTID_FINGERPRINT" || unknown == "Acoustid Fingerprint" => + { + continue + } + ItemKey::Unknown(unknown) => Tag::Key(unknown.to_string()), + custom => Tag::Key(format!("{:?}", custom)), + }; + + let value = match item.value() { + ItemValue::Text(value) => value.clone(), + ItemValue::Locator(value) => value.clone(), + ItemValue::Binary(bin) => format!("BIN#{}", general_purpose::STANDARD.encode(bin)), + }; + + tags.insert(key, value); + } + + // Get all the album artwork information from the file + let mut album_art: Vec = Vec::new(); + for (i, _art) in tag.pictures().iter().enumerate() { + let new_art = AlbumArt::Embedded(i); + + album_art.push(new_art) + } + + // Find images around the music file that can be used + let mut found_images = find_images(target_file).unwrap(); + album_art.append(&mut found_images); + + // Get the format as a string + let format: Option = match FileFormat::from_file(target_file) { + Ok(fmt) => Some(fmt), + Err(_) => None, + }; + + // TODO: Fix error handling + let binding = fs::canonicalize(target_file).unwrap(); + + let new_song = Song { + location: URI::Local(binding), + plays: 0, + skips: 0, + favorited: false, + rating: None, + format, + duration, + play_time: Duration::from_secs(0), + last_played: None, + date_added: Some(chrono::offset::Utc::now()), + date_modified: Some(chrono::offset::Utc::now()), + tags, + album_art, + }; + Ok(new_song) + } + + /// creates a `Vec` from a cue file + + pub fn from_cue(cuesheet: &Path) -> Result<(Vec<(Self, &PathBuf)>), Box> { + let mut tracks = Vec::new(); + + let cue_data = parse_from_file(&cuesheet.to_string_lossy(), false).unwrap(); + + // Get album level information + let album_title = &cue_data.title; + let album_artist = &cue_data.performer; + + let parent_dir = cuesheet.parent().expect("The file has no parent path??"); + for file in cue_data.files.iter() { + let audio_location = &parent_dir.join(file.file.clone()); + + if !audio_location.exists() { + continue; + } + + let next_track = file.tracks.clone(); + let mut next_track = next_track.iter().skip(1); + for (i, track) in file.tracks.iter().enumerate() { + // Get the track timing information + let pregap = match track.pregap { + Some(pregap) => pregap, + None => Duration::from_secs(0), + }; + let postgap = match track.postgap { + Some(postgap) => postgap, + None => Duration::from_secs(0), + }; + + let mut start; + if track.indices.len() > 1 { + start = track.indices[1].1; + } else { + start = track.indices[0].1; + } + if !start.is_zero() { + start -= pregap; + } + + let duration = match next_track.next() { + Some(future) => match future.indices.first() { + Some(val) => val.1 - start, + None => Duration::from_secs(0), + }, + None => match lofty::read_from_path(audio_location) { + Ok(tagged_file) => tagged_file.properties().duration() - start, + + Err(_) => match Probe::open(audio_location)?.read() { + Ok(tagged_file) => tagged_file.properties().duration() - start, + + Err(_) => Duration::from_secs(0), + }, + }, + }; + let end = start + duration + postgap; + + // Get the format as a string + let format: Option = match FileFormat::from_file(audio_location) { + Ok(fmt) => Some(fmt), + Err(_) => None, + }; + + // Get some useful tags + let mut tags: BTreeMap = BTreeMap::new(); + match album_title { + Some(title) => { + tags.insert(Tag::Album, title.clone()); + } + None => (), + } + match album_artist { + Some(artist) => { + tags.insert(Tag::Artist, artist.clone()); + } + None => (), + } + tags.insert(Tag::Track, track.no.parse().unwrap_or((i + 1).to_string())); + match track.title.clone() { + Some(title) => tags.insert(Tag::Title, title), + None => match track.isrc.clone() { + Some(title) => tags.insert(Tag::Title, title), + None => { + let namestr = format!("{} - {}", i, file.file.clone()); + tags.insert(Tag::Title, namestr) + } + }, + }; + match track.performer.clone() { + Some(artist) => tags.insert(Tag::Artist, artist), + None => None, + }; + + // Find images around the music file that can be used + let album_art = find_images(&audio_location.to_path_buf()).unwrap(); + + let new_song = Song { + location: URI::Cue { + location: audio_location.clone(), + index: i, + start, + end, + }, + plays: 0, + skips: 0, + favorited: false, + rating: None, + format, + duration, + play_time: Duration::from_secs(0), + last_played: None, + date_added: Some(chrono::offset::Utc::now()), + date_modified: Some(chrono::offset::Utc::now()), + tags, + album_art, + }; + tracks.push((new_song, audio_location)); + } + } + Ok((tracks)) + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -499,97 +718,8 @@ impl MusicLibrary { } pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box> { - let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed); - - let blank_tag = &lofty::Tag::new(TagType::Id3v2); - let tagged_file: lofty::TaggedFile; - let mut duration = Duration::from_secs(0); - let tag = match Probe::open(target_file)?.options(normal_options).read() { - Ok(file) => { - tagged_file = file; - - duration = tagged_file.properties().duration(); - - // Ensure the tags exist, if not, insert blank data - 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 mut tags: BTreeMap = BTreeMap::new(); - for item in tag.items() { - let key = match item.key() { - ItemKey::TrackTitle => Tag::Title, - ItemKey::TrackNumber => Tag::Track, - ItemKey::TrackArtist => Tag::Artist, - ItemKey::AlbumArtist => Tag::AlbumArtist, - ItemKey::Genre => Tag::Genre, - ItemKey::Comment => Tag::Comment, - ItemKey::AlbumTitle => Tag::Album, - ItemKey::DiscNumber => Tag::Disk, - ItemKey::Unknown(unknown) - if unknown == "ACOUSTID_FINGERPRINT" || unknown == "Acoustid Fingerprint" => - { - continue - } - ItemKey::Unknown(unknown) => Tag::Key(unknown.to_string()), - custom => Tag::Key(format!("{:?}", custom)), - }; - - let value = match item.value() { - ItemValue::Text(value) => value.clone(), - ItemValue::Locator(value) => value.clone(), - ItemValue::Binary(bin) => format!("BIN#{}", general_purpose::STANDARD.encode(bin)), - }; - - tags.insert(key, value); - } - - // Get all the album artwork information from the file - let mut album_art: Vec = Vec::new(); - for (i, _art) in tag.pictures().iter().enumerate() { - let new_art = AlbumArt::Embedded(i); - - album_art.push(new_art) - } - - // Find images around the music file that can be used - let mut found_images = find_images(target_file).unwrap(); - album_art.append(&mut found_images); - - // Get the format as a string - let format: Option = match FileFormat::from_file(target_file) { - Ok(fmt) => Some(fmt), - Err(_) => None, - }; - - // TODO: Fix error handling - let binding = fs::canonicalize(target_file).unwrap(); - - let new_song = Song { - location: URI::Local(binding), - plays: 0, - skips: 0, - favorited: false, - rating: None, - format, - duration, - play_time: Duration::from_secs(0), - last_played: None, - date_added: Some(chrono::offset::Utc::now()), - date_modified: Some(chrono::offset::Utc::now()), - tags, - album_art, - }; + let new_song = Song::from_file(target_file)?; match self.add_song(new_song) { Ok(_) => (), Err(_) => { @@ -601,137 +731,23 @@ impl MusicLibrary { } pub fn add_cuesheet(&mut self, cuesheet: &Path) -> Result> { - let mut tracks_added = 0; + let tracks = Song::from_cue(cuesheet)?; + let mut tracks_added = tracks.len() as i32; - let cue_data = parse_from_file(&cuesheet.to_string_lossy(), false).unwrap(); - - // Get album level information - let album_title = &cue_data.title; - let album_artist = &cue_data.performer; - - let parent_dir = cuesheet.parent().expect("The file has no parent path??"); - for file in cue_data.files.iter() { - let audio_location = &parent_dir.join(file.file.clone()); - - if !audio_location.exists() { - continue; - } + 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(audio_location.clone())).is_ok() { + if self.remove_uri(&URI::Local(location.clone())).is_ok() { tracks_added -= 1 } - - let next_track = file.tracks.clone(); - let mut next_track = next_track.iter().skip(1); - for (i, track) in file.tracks.iter().enumerate() { - // Get the track timing information - let pregap = match track.pregap { - Some(pregap) => pregap, - None => Duration::from_secs(0), - }; - let postgap = match track.postgap { - Some(postgap) => postgap, - None => Duration::from_secs(0), - }; - - let mut start; - if track.indices.len() > 1 { - start = track.indices[1].1; - } else { - start = track.indices[0].1; + match self.add_song(new_song) { + Ok(_) => {}, + Err(_error) => { + //println!("{}", _error); + continue; } - if !start.is_zero() { - start -= pregap; - } - - let duration = match next_track.next() { - Some(future) => match future.indices.first() { - Some(val) => val.1 - start, - None => Duration::from_secs(0), - }, - None => match lofty::read_from_path(audio_location) { - Ok(tagged_file) => tagged_file.properties().duration() - start, - - Err(_) => match Probe::open(audio_location)?.read() { - Ok(tagged_file) => tagged_file.properties().duration() - start, - - Err(_) => Duration::from_secs(0), - }, - }, - }; - let end = start + duration + postgap; - - // Get the format as a string - let format: Option = match FileFormat::from_file(audio_location) { - Ok(fmt) => Some(fmt), - Err(_) => None, - }; - - // Get some useful tags - let mut tags: BTreeMap = BTreeMap::new(); - match album_title { - Some(title) => { - tags.insert(Tag::Album, title.clone()); - } - None => (), - } - match album_artist { - Some(artist) => { - tags.insert(Tag::Artist, artist.clone()); - } - None => (), - } - tags.insert(Tag::Track, track.no.parse().unwrap_or((i + 1).to_string())); - match track.title.clone() { - Some(title) => tags.insert(Tag::Title, title), - None => match track.isrc.clone() { - Some(title) => tags.insert(Tag::Title, title), - None => { - let namestr = format!("{} - {}", i, file.file.clone()); - tags.insert(Tag::Title, namestr) - } - }, - }; - match track.performer.clone() { - Some(artist) => tags.insert(Tag::Artist, artist), - None => None, - }; - - // Find images around the music file that can be used - let album_art = find_images(&audio_location.to_path_buf()).unwrap(); - - let new_song = Song { - location: URI::Cue { - location: audio_location.clone(), - index: i, - start, - end, - }, - plays: 0, - skips: 0, - favorited: false, - rating: None, - format, - duration, - play_time: Duration::from_secs(0), - last_played: None, - date_added: Some(chrono::offset::Utc::now()), - date_modified: Some(chrono::offset::Utc::now()), - tags, - album_art, - }; - - match self.add_song(new_song) { - Ok(_) => tracks_added += 1, - Err(_error) => { - //println!("{}", _error); - continue; - } - }; - } + }; } - Ok(tracks_added) } diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs index 730af56..c9ad352 100644 --- a/src/music_storage/playlist.rs +++ b/src/music_storage/playlist.rs @@ -1,7 +1,9 @@ -use std::path::Path; +use std::{fs::File, path::{Path, PathBuf}, io::{Read, Error}}; +use bincode::config; use chrono::Duration; -use walkdir::Error; +use uuid::Uuid; +// use walkdir::Error; use super::{ library::{AlbumArt, Song, Tag}, @@ -11,14 +13,14 @@ use super::{ }, }; -use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment}; -// use nom::IResult; +use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2, MasterPlaylist}; + #[derive(Debug, Clone)] pub struct Playlist<'a> { title: String, cover: Option<&'a AlbumArt>, - tracks: Vec, + tracks: Vec, play_count: i32, play_time: Duration, } @@ -32,43 +34,37 @@ impl<'a> Playlist<'a> { pub fn play_time(&self) -> chrono::Duration { self.play_time } - pub fn set_tracks(&mut self, songs: Vec) -> Result<(), Error> { + pub fn set_tracks(&mut self, tracks: Vec) { self.tracks = songs; - Ok(()) } - pub fn add_track(&mut self, song: Song) -> Result<(), Error> { + pub fn add_track(&mut self, track: Uuid) -> Result<(), Error> { self.tracks.push(song); Ok(()) } pub fn remove_track(&mut self, index: i32) -> Result<(), Error> { - let bun: usize = index as usize; - let mut name = String::new(); - if self.tracks.len() >= bun { - name = String::from(self.tracks[bun].tags.get_key_value(&Tag::Title).unwrap().1); - self.tracks.remove(bun); + let index = index as usize; + if (self.tracks.len() - 1) >= index { + self.tracks.remove(index); } - dbg!(name); Ok(()) } - pub fn get_index(&self, song_name: &str) -> Option { - let mut index = 0; - if self.contains_value(&Tag::Title, song_name) { - for track in &self.tracks { - index += 1; - if song_name == track.tags.get_key_value(&Tag::Title).unwrap().1 { - dbg!("Index gotted! ", index); - return Some(index); - } - } - } - None - } + // pub fn get_index(&self, song_name: &str) -> Option { + // let mut index = 0; + // if self.contains_value(&Tag::Title, song_name) { + // for track in &self.tracks { + // index += 1; + // if song_name == track.tags.get_key_value(&Tag::Title).unwrap().1 { + // dbg!("Index gotted! ", index); + // return Some(index); + // } + // } + // } + // None + // } pub fn contains_value(&self, tag: &Tag, value: &str) -> bool { - for track in &self.tracks { - if value == track.tags.get_key_value(tag).unwrap().1 { - return true; - } - } + &self.tracks.iter().for_each(|track| { + + }); false } pub fn to_m3u8(&mut self) { @@ -104,7 +100,28 @@ impl<'a> Playlist<'a> { .unwrap(); m3u8.write_to(&mut file).unwrap(); } - pub fn from_m3u8(file: std::fs::File) -> Playlist<'a> { + pub fn from_m3u8(path: &str) -> Result, Error> { + let mut file = match File::open(path) { + Ok(file) => file, + Err(e) => return Err(e), + }; + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes).unwrap(); + + let parsed = m3u8_rs::parse_playlist(&bytes); + + let playlist = match parsed { + Result::Ok((i, 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::>(); + } + } + todo!() } }