BazNew/src/songmeta.rs

186 lines
5.8 KiB
Rust

///Module used to read audio files into SongMetas, which is a collection of data about the file.
///This is made primarily for music and stores informationa about artist, album, etc.
pub mod songmeta {
use std::path::{PathBuf, Path};
use std::error::Error;
use std::fmt;
use regex::Regex;
use std::ffi::OsStr;
use metadata::media_file::MediaFileMetadata;
use std::time::Duration;
use file_format::FileFormat;
use serde::{Deserialize, Serialize};
use std::io::Write;
//----------------------------ERROR
#[derive(Debug)]
pub enum SongError {
///Given path is not existing or is not a media file.
InvalidPath,
///Given path is not an audio file
NotAudio,
}
impl fmt::Display for SongError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SongError::InvalidPath => write!(f, "Path does not exist or is not a media"),
SongError::NotAudio => write!(f, "Path exists but is not an audio file"),
}
}
}
impl Error for SongError {}
//----------------------------STRUCT
///Stores metadata about a song, as well as the path of the file and eventually of the song's
///album art (foudn automatically by searching for "cover.png"
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SongMeta {
pub title: String,
pub artist: String,
pub album: String,
pub track: usize,
pub duration: Duration,
pub cover_path: Option<String>,
pub path: String,
}
impl SongMeta {
///Returns a SongMeta from the path of an audio file.
///Returns an error if need be.
///Also looks for the path of the cover, which may be None.
pub fn frompath(path: &String) -> Result<Self, Box<dyn Error>> {
if !SongMeta::validate_file(path) {
return Err(Box::new(SongError::NotAudio));
}
let fpath = Path::new(path);
let md = match MediaFileMetadata::new(&fpath) {
Ok(v) => v,
Err(e) => {
return Err(Box::new(SongError::InvalidPath));
},
};
//cover
let cover_path = match Self::find_cover_path(path.clone()) {
Some(v) => Some(v),
None => None,
};
//duration
let length = md._duration.clone().unwrap_or(0.0) as u64;
let duration = Duration::from_secs(length);
//title
let filename = fpath.file_name().unwrap_or(&OsStr::new("Unkown title"));
let filename = String::from(filename.to_str().unwrap_or("Unkown"));
let title = md.title.clone().unwrap_or(filename.clone());
//other
let mut artist = String::from("Artist unkown");
let mut album = String::from("Album unkown");
let mut trackorder: usize = 0;
let mut track_in_name: usize = 0;
//Find track order in filename first
if let Some(nbr) = Self::find_nbr_in_filename(&filename) {
track_in_name = nbr;
}
for i in md.filtered_tags {
if i.0.to_lowercase() == "artist" {
artist = i.1.clone();
}
if i.0.to_lowercase() == "album" {
album = i.1.clone();
}
if i.0.to_lowercase() == "track" {
trackorder = i.1.clone().parse().unwrap_or(track_in_name);
}
}
let val = SongMeta {
title: title,
artist: artist,
album: album,
track: trackorder,
duration: duration,
path: path.clone(),
cover_path: cover_path,
};
Ok(val)
}
///Looks for a cover path (cover.jpg or cover.png)
fn find_cover_path(path: String) -> Option<String> {
let possible_paths = vec!["cover.jpg", "cover.png"];
let mut foundpath = String::new();
let mut found = false;
let mut p = PathBuf::from(&path);
if !p.pop() {
return None;
}
//Search...
for poss in possible_paths {
let mut test = p.clone();
test.push(Path::new(poss));
if test.exists() {
foundpath = test.to_string_lossy().into_owned().clone();
found = true;
}
}
if found {
return Some(foundpath);
}
return None;
}
///Tries to find a logical track number in the filename,
///at the beginning of it.
fn find_nbr_in_filename(filename: &String) -> Option<usize> {
//"^[0-9]+"
let regex = Regex::new(r"(?<tracknbr>^[0-9]+)").unwrap();
let cap = regex.captures(filename.as_str());
if let Some(found) = cap {
return Some(found["tracknbr"].to_string().parse().unwrap_or(0) as usize);
}
return None;
}
///Validates that the file at path is audio.
fn validate_file(path: &String) -> bool {
let valid_formats = vec![
"audio/mpeg",
"audio/x-flac",
"audio/ogg",
"audio/vnd.wave",
"audio/aac",
];
let fmt = match FileFormat::from_file(path.as_str()) {
Ok(v) => v,
Err(_) => {
return false;
}
};
if valid_formats.contains(&fmt.media_type()) {
return true;
}
return false;
}
}
}