186 lines
5.8 KiB
Rust
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|