moved Song creation functions to the Song struct

This commit is contained in:
MrDulfin 2024-02-06 20:36:31 -05:00
parent f751ec6bbf
commit 0a68a12546
3 changed files with 285 additions and 248 deletions

View file

@ -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,
}

View file

@ -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<Self, Box<dyn Error>> {
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<Tag, String> = 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<AlbumArt> = 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<FileFormat> = 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<Song>` from a cue file
pub fn from_cue(cuesheet: &Path) -> Result<(Vec<(Self, &PathBuf)>), Box<dyn Error>> {
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<FileFormat> = match FileFormat::from_file(audio_location) {
Ok(fmt) => Some(fmt),
Err(_) => None,
};
// Get some useful tags
let mut tags: BTreeMap<Tag, String> = 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<dyn Error>> {
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<Tag, String> = 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<AlbumArt> = 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<FileFormat> = 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<i32, Box<dyn Error>> {
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<FileFormat> = match FileFormat::from_file(audio_location) {
Ok(fmt) => Some(fmt),
Err(_) => None,
};
// Get some useful tags
let mut tags: BTreeMap<Tag, String> = 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)
}

View file

@ -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<Song>,
tracks: Vec<Uuid>,
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<Song>) -> Result<(), Error> {
pub fn set_tracks(&mut self, tracks: Vec<Uuid>) {
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<usize> {
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<usize> {
// 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<Playlist<'a>, 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::<Vec<String>>();
}
}
todo!()
}
}