mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 10:02:53 -05:00
A bunch of improvements
This commit is contained in:
parent
7ddc829dac
commit
2e0ce48506
7 changed files with 101 additions and 150 deletions
|
@ -38,7 +38,6 @@ rayon = "1.8.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.4"
|
||||||
cue = "2.0.0"
|
cue = "2.0.0"
|
||||||
jwalk = "0.8.1"
|
|
||||||
base64 = "0.21.5"
|
base64 = "0.21.5"
|
||||||
zip = "0.6.6"
|
zip = "0.6.6"
|
||||||
flate2 = "1.0.28"
|
flate2 = "1.0.28"
|
||||||
|
|
|
@ -5,11 +5,7 @@ pub mod music_tracker {
|
||||||
pub mod music_storage {
|
pub mod music_storage {
|
||||||
pub mod music_db;
|
pub mod music_db;
|
||||||
pub mod playlist;
|
pub mod playlist;
|
||||||
pub mod utils;
|
mod utils;
|
||||||
}
|
|
||||||
|
|
||||||
pub mod music_processor {
|
|
||||||
pub mod music_processor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod music_player {
|
pub mod music_player {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::path::PathBuf;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use crate::music_controller::config::Config;
|
use crate::music_controller::config::Config;
|
||||||
use crate::music_player::music_player::{DSPMessage, DecoderMessage, MusicPlayer, PlayerStatus};
|
use crate::music_player::music_player::{DecoderMessage, MusicPlayer, PlayerStatus};
|
||||||
use crate::music_storage::music_db::{MusicLibrary, Song, Tag};
|
use crate::music_storage::music_db::{MusicLibrary, Song, Tag};
|
||||||
|
|
||||||
pub struct MusicController {
|
pub struct MusicController {
|
||||||
|
@ -63,19 +63,6 @@ impl MusicController {
|
||||||
return self.music_player.get_current_song();
|
return self.music_player.get_current_song();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets audio playback volume
|
|
||||||
pub fn get_vol(&self) -> f32 {
|
|
||||||
return self.music_player.music_processor.audio_volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets audio playback volume on a scale of 0.0 to 1.0
|
|
||||||
pub fn set_vol(&mut self, volume: f32) {
|
|
||||||
self.music_player.music_processor.audio_volume = volume;
|
|
||||||
self.song_control(DecoderMessage::DSP(DSPMessage::UpdateProcessor(Box::new(
|
|
||||||
self.music_player.music_processor.clone(),
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queries the [MusicLibrary], returning a `Vec<Song>`
|
/// Queries the [MusicLibrary], returning a `Vec<Song>`
|
||||||
pub fn query_library(
|
pub fn query_library(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -19,7 +19,6 @@ use futures::AsyncBufRead;
|
||||||
|
|
||||||
use crate::music_controller::config::Config;
|
use crate::music_controller::config::Config;
|
||||||
use crate::music_player::music_output::AudioStream;
|
use crate::music_player::music_output::AudioStream;
|
||||||
use crate::music_processor::music_processor::MusicProcessor;
|
|
||||||
use crate::music_storage::music_db::{Song, URI};
|
use crate::music_storage::music_db::{Song, URI};
|
||||||
use crate::music_tracker::music_tracker::{
|
use crate::music_tracker::music_tracker::{
|
||||||
DiscordRPC, LastFM, ListenBrainz, MusicTracker, TrackerError,
|
DiscordRPC, LastFM, ListenBrainz, MusicTracker, TrackerError,
|
||||||
|
@ -27,7 +26,6 @@ use crate::music_tracker::music_tracker::{
|
||||||
|
|
||||||
// Struct that controls playback of music
|
// Struct that controls playback of music
|
||||||
pub struct MusicPlayer {
|
pub struct MusicPlayer {
|
||||||
pub music_processor: MusicProcessor,
|
|
||||||
player_status: PlayerStatus,
|
player_status: PlayerStatus,
|
||||||
music_trackers: Vec<Box<dyn MusicTracker + Send>>,
|
music_trackers: Vec<Box<dyn MusicTracker + Send>>,
|
||||||
current_song: Arc<RwLock<Option<Song>>>,
|
current_song: Arc<RwLock<Option<Song>>>,
|
||||||
|
@ -51,7 +49,6 @@ pub enum DecoderMessage {
|
||||||
Pause,
|
Pause,
|
||||||
Stop,
|
Stop,
|
||||||
SeekTo(u64),
|
SeekTo(u64),
|
||||||
DSP(DSPMessage),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -60,11 +57,6 @@ pub enum TrackerMessage {
|
||||||
TrackNow(Song),
|
TrackNow(Song),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum DSPMessage {
|
|
||||||
UpdateProcessor(Box<MusicProcessor>),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Holds a song decoder reader, etc
|
// Holds a song decoder reader, etc
|
||||||
struct SongHandler {
|
struct SongHandler {
|
||||||
pub reader: Box<dyn FormatReader>,
|
pub reader: Box<dyn FormatReader>,
|
||||||
|
@ -148,7 +140,6 @@ impl MusicPlayer {
|
||||||
);
|
);
|
||||||
|
|
||||||
MusicPlayer {
|
MusicPlayer {
|
||||||
music_processor: MusicProcessor::new(),
|
|
||||||
music_trackers: Vec::new(),
|
music_trackers: Vec::new(),
|
||||||
player_status: PlayerStatus::Stopped,
|
player_status: PlayerStatus::Stopped,
|
||||||
current_song,
|
current_song,
|
||||||
|
@ -235,8 +226,6 @@ impl MusicPlayer {
|
||||||
|
|
||||||
let mut audio_output: Option<Box<dyn AudioStream>> = None;
|
let mut audio_output: Option<Box<dyn AudioStream>> = None;
|
||||||
|
|
||||||
let mut music_processor = MusicProcessor::new();
|
|
||||||
|
|
||||||
let (tracker_sender, tracker_receiver): (
|
let (tracker_sender, tracker_receiver): (
|
||||||
Sender<TrackerMessage>,
|
Sender<TrackerMessage>,
|
||||||
Receiver<TrackerMessage>,
|
Receiver<TrackerMessage>,
|
||||||
|
@ -290,11 +279,6 @@ impl MusicPlayer {
|
||||||
status_sender.send(PlayerStatus::Paused).unwrap();
|
status_sender.send(PlayerStatus::Paused).unwrap();
|
||||||
}
|
}
|
||||||
Some(DecoderMessage::SeekTo(time)) => seek_time = Some(time),
|
Some(DecoderMessage::SeekTo(time)) => seek_time = Some(time),
|
||||||
Some(DecoderMessage::DSP(dsp_message)) => match dsp_message {
|
|
||||||
DSPMessage::UpdateProcessor(new_processor) => {
|
|
||||||
music_processor = *new_processor
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Exits main decode loop and subsequently ends thread
|
// Exits main decode loop and subsequently ends thread
|
||||||
Some(DecoderMessage::Stop) => {
|
Some(DecoderMessage::Stop) => {
|
||||||
status_sender.send(PlayerStatus::Stopped).unwrap();
|
status_sender.send(PlayerStatus::Stopped).unwrap();
|
||||||
|
@ -373,22 +357,6 @@ impl MusicPlayer {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Handles audio normally provided there is an audio stream
|
|
||||||
if let Some(ref mut audio_output) = audio_output {
|
|
||||||
// Changes buffer of the MusicProcessor if the packet has a differing capacity or spec
|
|
||||||
if music_processor.audio_buffer.capacity() != decoded.capacity()
|
|
||||||
|| music_processor.audio_buffer.spec() != decoded.spec()
|
|
||||||
{
|
|
||||||
let spec = *decoded.spec();
|
|
||||||
let duration = decoded.capacity() as u64;
|
|
||||||
|
|
||||||
music_processor.set_buffer(duration, spec);
|
|
||||||
}
|
|
||||||
let transformed_audio = music_processor.process(&decoded);
|
|
||||||
|
|
||||||
// Writes transformed packet to audio out
|
|
||||||
audio_output.write(transformed_audio).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(Error::IoError(_)) => {
|
Err(Error::IoError(_)) => {
|
||||||
// rest in peace packet
|
// rest in peace packet
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use symphonia::core::audio::{AsAudioBufferRef, AudioBuffer, AudioBufferRef, Signal, SignalSpec};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MusicProcessor {
|
|
||||||
pub audio_buffer: AudioBuffer<f32>,
|
|
||||||
pub audio_volume: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for MusicProcessor {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("MusicProcessor")
|
|
||||||
.field("audio_volume", &self.audio_volume)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MusicProcessor {
|
|
||||||
/// Returns new MusicProcessor with blank buffer and 100% volume
|
|
||||||
pub fn new() -> Self {
|
|
||||||
MusicProcessor {
|
|
||||||
audio_buffer: AudioBuffer::unused(),
|
|
||||||
audio_volume: 1.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Processes audio samples
|
|
||||||
///
|
|
||||||
/// Currently only supports transformations of volume
|
|
||||||
pub fn process(&mut self, audio_buffer_ref: &AudioBufferRef) -> AudioBufferRef {
|
|
||||||
audio_buffer_ref.convert(&mut self.audio_buffer);
|
|
||||||
|
|
||||||
let process = |sample| sample * self.audio_volume;
|
|
||||||
|
|
||||||
self.audio_buffer.transform(process);
|
|
||||||
|
|
||||||
return self.audio_buffer.as_audio_buffer_ref();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets buffer of the MusicProcessor
|
|
||||||
pub fn set_buffer(&mut self, duration: u64, spec: SignalSpec) {
|
|
||||||
self.audio_buffer = AudioBuffer::new(duration, spec);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +1,17 @@
|
||||||
// Crate things
|
// Crate things
|
||||||
use super::utils::{normalize, read_library, write_library};
|
use super::utils::{normalize, read_library, write_library, find_images};
|
||||||
use crate::music_controller::config::Config;
|
use crate::music_controller::config::Config;
|
||||||
|
|
||||||
// Various std things
|
// Various std things
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::ops::ControlFlow::{Break, Continue};
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
use cue::cd::CD;
|
use cue::cd::CD;
|
||||||
use file_format::{FileFormat, Kind};
|
use file_format::{FileFormat, Kind};
|
||||||
use jwalk::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
|
use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
@ -28,9 +28,9 @@ use rayon::prelude::*;
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct AlbumArt {
|
pub enum AlbumArt {
|
||||||
pub index: u16,
|
Embedded(usize),
|
||||||
pub path: Option<URI>,
|
External(URI),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
@ -112,7 +112,7 @@ impl Song {
|
||||||
},
|
},
|
||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
_ => None, // Other field types are not yet supported
|
_ => todo!(), // Other field types are not yet supported
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,6 +182,7 @@ pub enum Service {
|
||||||
InternetRadio,
|
InternetRadio,
|
||||||
Spotify,
|
Spotify,
|
||||||
Youtube,
|
Youtube,
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -236,6 +237,8 @@ impl Album<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BLOCKED_EXTENSIONS: [&str; 3] = ["vob", "log", "txt"];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MusicLibrary {
|
pub struct MusicLibrary {
|
||||||
pub library: Vec<Song>,
|
pub library: Vec<Song>,
|
||||||
|
@ -272,8 +275,7 @@ impl MusicLibrary {
|
||||||
Ok(Self { library })
|
Ok(Self { library })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes the database out to the file
|
/// Serializes the database out to the file specified in the config
|
||||||
/// specified in the config
|
|
||||||
pub fn save(&self, config: &Config) -> Result<(), Box<dyn Error>> {
|
pub fn save(&self, config: &Config) -> Result<(), Box<dyn Error>> {
|
||||||
match config.db_path.try_exists() {
|
match config.db_path.try_exists() {
|
||||||
Ok(exists) => {
|
Ok(exists) => {
|
||||||
|
@ -293,19 +295,16 @@ impl MusicLibrary {
|
||||||
/// Queries for a [Song] by its [URI], returning a single `Song`
|
/// Queries for a [Song] by its [URI], returning a single `Song`
|
||||||
/// with the `URI` that matches
|
/// with the `URI` that matches
|
||||||
fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
|
fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
|
||||||
let result = Arc::new(Mutex::new(None));
|
let result = self.library.par_iter().enumerate().try_for_each(|(i, track)| {
|
||||||
let index = Arc::new(Mutex::new(0));
|
|
||||||
let _ = &self.library.par_iter().enumerate().for_each(|(i, track)| {
|
|
||||||
if path == &track.location {
|
if path == &track.location {
|
||||||
*result.clone().lock().unwrap() = Some(track);
|
return std::ops::ControlFlow::Break((track, i));
|
||||||
*index.clone().lock().unwrap() = i;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
Continue(())
|
||||||
});
|
});
|
||||||
let song = Arc::try_unwrap(result).unwrap().into_inner().unwrap();
|
|
||||||
match song {
|
match result {
|
||||||
Some(song) => Some((song, Arc::try_unwrap(index).unwrap().into_inner().unwrap())),
|
Break(song) => Some(song),
|
||||||
None => None,
|
Continue(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,7 +312,7 @@ impl MusicLibrary {
|
||||||
/// with matching `PathBuf`s
|
/// with matching `PathBuf`s
|
||||||
fn query_path(&self, path: &PathBuf) -> Option<Vec<&Song>> {
|
fn query_path(&self, path: &PathBuf) -> Option<Vec<&Song>> {
|
||||||
let result: Arc<Mutex<Vec<&Song>>> = Arc::new(Mutex::new(Vec::new()));
|
let result: Arc<Mutex<Vec<&Song>>> = Arc::new(Mutex::new(Vec::new()));
|
||||||
let _ = &self.library.par_iter().for_each(|track| {
|
let _ = self.library.par_iter().for_each(|track| {
|
||||||
if path == track.location.path() {
|
if path == track.location.path() {
|
||||||
result.clone().lock().unwrap().push(&track);
|
result.clone().lock().unwrap().push(&track);
|
||||||
return;
|
return;
|
||||||
|
@ -326,20 +325,18 @@ impl MusicLibrary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds all the music files within a specified folder
|
/// Finds all the audio files within a specified folder
|
||||||
pub fn scan_folder(
|
pub fn scan_folder(
|
||||||
&mut self,
|
&mut self,
|
||||||
target_path: &str,
|
target_path: &str,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<usize, Box<dyn std::error::Error>> {
|
) -> Result<usize, Box<dyn std::error::Error>> {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
let mut i = 0;
|
for target_file in WalkDir::new(target_path)
|
||||||
for entry in WalkDir::new(target_path)
|
|
||||||
.follow_links(true)
|
.follow_links(true)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
{
|
{
|
||||||
let target_file = entry;
|
|
||||||
let path = target_file.path();
|
let path = target_file.path();
|
||||||
|
|
||||||
// Ensure the target is a file and not a directory,
|
// Ensure the target is a file and not a directory,
|
||||||
|
@ -348,6 +345,7 @@ impl MusicLibrary {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: figure out how to increase the speed of this maybe
|
||||||
// Check if the file path is already in the db
|
// Check if the file path is already in the db
|
||||||
if self.query_uri(&URI::Local(path.to_path_buf())).is_some() {
|
if self.query_uri(&URI::Local(path.to_path_buf())).is_some() {
|
||||||
continue;
|
continue;
|
||||||
|
@ -358,30 +356,27 @@ impl MusicLibrary {
|
||||||
if i % 500 == 0 {
|
if i % 500 == 0 {
|
||||||
self.save(config).unwrap();
|
self.save(config).unwrap();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
let format = FileFormat::from_file(&path)?;
|
let format = FileFormat::from_file(&path)?;
|
||||||
let extension: &OsStr = match path.extension() {
|
let extension = match path.extension() {
|
||||||
Some(ext) => ext,
|
Some(ext) => ext.to_string_lossy().to_ascii_lowercase(),
|
||||||
None => OsStr::new(""),
|
None => String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// If it's a normal file, add it to the database
|
// If it's a normal file, add it to the database
|
||||||
// if it's a cuesheet, do a bunch of fancy stuff
|
// if it's a cuesheet, do a bunch of fancy stuff
|
||||||
if (format.kind() == Kind::Audio || format.kind() == Kind::Video)
|
if (format.kind() == Kind::Audio || format.kind() == Kind::Video)
|
||||||
&& extension.to_ascii_lowercase() != "log"
|
&& !BLOCKED_EXTENSIONS.contains(&extension.as_str())
|
||||||
&& extension.to_ascii_lowercase() != "vob"
|
|
||||||
{
|
{
|
||||||
match self.add_file(&target_file.path()) {
|
match self.add_file(&target_file.path()) {
|
||||||
Ok(_) => {
|
Ok(_) => total += 1,
|
||||||
//println!("{:?}", target_file.path());
|
|
||||||
total += 1
|
|
||||||
}
|
|
||||||
Err(_error) => {
|
Err(_error) => {
|
||||||
//println!("{}, {:?}: {}", format, target_file.file_name(), _error)
|
println!("{}, {:?}: {}", format, target_file.file_name(), _error)
|
||||||
} // TODO: Handle more of these errors
|
} // TODO: Handle more of these errors
|
||||||
};
|
};
|
||||||
} else if extension.to_ascii_lowercase() == "cue" {
|
} else if extension == "cue" {
|
||||||
total += match self.add_cuesheet(&target_file.path()) {
|
total += match self.add_cuesheet(&target_file.path().to_path_buf()) {
|
||||||
Ok(added) => added,
|
Ok(added) => added,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
println!("{}", error);
|
println!("{}", error);
|
||||||
|
@ -437,25 +432,26 @@ impl MusicLibrary {
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = match item.value() {
|
let value = match item.value() {
|
||||||
ItemValue::Text(value) => String::from(value),
|
ItemValue::Text(value) => value.clone(),
|
||||||
ItemValue::Locator(value) => String::from(value),
|
ItemValue::Locator(value) => value.clone(),
|
||||||
ItemValue::Binary(bin) => format!("BIN#{}", general_purpose::STANDARD.encode(bin)),
|
ItemValue::Binary(bin) => format!("BIN#{}", general_purpose::STANDARD.encode(bin)),
|
||||||
};
|
};
|
||||||
|
|
||||||
tags.insert(key, value);
|
tags.insert(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all the album artwork information
|
// Get all the album artwork information from the file
|
||||||
let mut album_art: Vec<AlbumArt> = Vec::new();
|
let mut album_art: Vec<AlbumArt> = Vec::new();
|
||||||
for (i, _art) in tag.pictures().iter().enumerate() {
|
for (i, _art) in tag.pictures().iter().enumerate() {
|
||||||
let new_art = AlbumArt {
|
let new_art = AlbumArt::Embedded(i as usize);
|
||||||
index: i as u16,
|
|
||||||
path: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
album_art.push(new_art)
|
album_art.push(new_art)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find images around the music file that can be used
|
||||||
|
let mut found_images = find_images(&target_file.to_path_buf()).unwrap();
|
||||||
|
album_art.append(&mut found_images);
|
||||||
|
|
||||||
// Get the format as a string
|
// Get the format as a string
|
||||||
let format: Option<FileFormat> = match FileFormat::from_file(target_file) {
|
let format: Option<FileFormat> = match FileFormat::from_file(target_file) {
|
||||||
Ok(fmt) => Some(fmt),
|
Ok(fmt) => Some(fmt),
|
||||||
|
@ -515,7 +511,10 @@ impl MusicLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to remove the original audio file from the db if it exists
|
// Try to remove the original audio file from the db if it exists
|
||||||
let _ = self.remove_uri(&URI::Local(audio_location.clone()));
|
match self.remove_uri(&URI::Local(audio_location.clone())) {
|
||||||
|
Ok(_) => tracks_added -= 1,
|
||||||
|
Err(_) => ()
|
||||||
|
};
|
||||||
|
|
||||||
// Get the track timing information
|
// Get the track timing information
|
||||||
let pregap = match track.get_zero_pre() {
|
let pregap = match track.get_zero_pre() {
|
||||||
|
@ -687,6 +686,7 @@ impl MusicLibrary {
|
||||||
sort_by: &Vec<Tag>, // Tags to sort the resulting data by
|
sort_by: &Vec<Tag>, // Tags to sort the resulting data by
|
||||||
) -> Option<Vec<&Song>> {
|
) -> Option<Vec<&Song>> {
|
||||||
let songs = Arc::new(Mutex::new(Vec::new()));
|
let songs = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
//let matcher = SkimMatcherV2::default();
|
||||||
|
|
||||||
self.library.par_iter().for_each(|track| {
|
self.library.par_iter().for_each(|track| {
|
||||||
for tag in target_tags {
|
for tag in target_tags {
|
||||||
|
@ -701,9 +701,19 @@ impl MusicLibrary {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if normalize(&track_result.to_string())
|
/*
|
||||||
.contains(&normalize(&query_string.to_owned()))
|
let match_level = match matcher.fuzzy_match(&normalize(&track_result), &normalize(query_string)) {
|
||||||
{
|
Some(conf) => conf,
|
||||||
|
None => continue
|
||||||
|
};
|
||||||
|
|
||||||
|
if match_level > 100 {
|
||||||
|
songs.lock().unwrap().push(track);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if normalize(&track_result.to_string()).contains(&normalize(&query_string.to_owned())) {
|
||||||
songs.lock().unwrap().push(track);
|
songs.lock().unwrap().push(track);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -776,6 +786,7 @@ impl MusicLibrary {
|
||||||
.unwrap_or(&"".to_string())
|
.unwrap_or(&"".to_string())
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap_or(1);
|
.unwrap_or(1);
|
||||||
|
|
||||||
match albums.get_mut(&norm_title) {
|
match albums.get_mut(&norm_title) {
|
||||||
// If the album is in the list, add the track to the appropriate disc in it
|
// If the album is in the list, add the track to the appropriate disc in it
|
||||||
Some(album) => match album.discs.get_mut(&disc_num) {
|
Some(album) => match album.discs.get_mut(&disc_num) {
|
||||||
|
@ -786,11 +797,13 @@ impl MusicLibrary {
|
||||||
},
|
},
|
||||||
// If the album is not in the list, make a new one and add it
|
// If the album is not in the list, make a new one and add it
|
||||||
None => {
|
None => {
|
||||||
|
let album_art = result.album_art.get(0);
|
||||||
|
|
||||||
let new_album = Album {
|
let new_album = Album {
|
||||||
title,
|
title,
|
||||||
artist: result.get_tag(&Tag::AlbumArtist),
|
artist: result.get_tag(&Tag::AlbumArtist),
|
||||||
discs: BTreeMap::from([(disc_num, vec![result])]),
|
discs: BTreeMap::from([(disc_num, vec![result])]),
|
||||||
cover: None,
|
cover: album_art,
|
||||||
};
|
};
|
||||||
albums.insert(norm_title, new_album);
|
albums.insert(norm_title, new_album);
|
||||||
}
|
}
|
||||||
|
@ -824,6 +837,7 @@ impl MusicLibrary {
|
||||||
albums
|
albums
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Queries a list of albums by title
|
||||||
pub fn query_albums(
|
pub fn query_albums(
|
||||||
&self,
|
&self,
|
||||||
query_string: &String, // The query itself
|
query_string: &String, // The query itself
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use std::io::{BufReader, BufWriter};
|
use std::io::{BufReader, BufWriter};
|
||||||
use std::{error::Error, fs, path::PathBuf};
|
use std::{error::Error, fs, path::PathBuf};
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
use file_format::{FileFormat, Kind};
|
||||||
|
|
||||||
use snap;
|
use snap;
|
||||||
|
|
||||||
use crate::music_storage::music_db::Song;
|
use super::music_db::{Song, AlbumArt, URI};
|
||||||
use unidecode::unidecode;
|
use unidecode::unidecode;
|
||||||
|
|
||||||
pub fn normalize(input_string: &String) -> String {
|
pub(super) fn normalize(input_string: &String) -> String {
|
||||||
// Normalize the unicode and convert everything to lowercase
|
|
||||||
let mut normalized = unidecode(input_string);
|
let mut normalized = unidecode(input_string);
|
||||||
|
|
||||||
// Remove non alphanumeric characters
|
// Remove non alphanumeric characters
|
||||||
|
@ -16,7 +17,7 @@ pub fn normalize(input_string: &String) -> String {
|
||||||
normalized
|
normalized
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
|
pub(super) fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
|
||||||
// Create a new snap reader over the database file
|
// Create a new snap reader over the database file
|
||||||
let database = fs::File::open(path)?;
|
let database = fs::File::open(path)?;
|
||||||
let reader = BufReader::new(database);
|
let reader = BufReader::new(database);
|
||||||
|
@ -32,7 +33,7 @@ pub fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
|
||||||
Ok(library)
|
Ok(library)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_library(
|
pub(super) fn write_library(
|
||||||
library: &Vec<Song>,
|
library: &Vec<Song>,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
take_backup: bool,
|
take_backup: bool,
|
||||||
|
@ -63,3 +64,34 @@ pub fn write_library(
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_images(song_path: &PathBuf) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
|
||||||
|
let mut images: Vec<AlbumArt> = Vec::new();
|
||||||
|
|
||||||
|
let song_dir = song_path.parent().ok_or("")?;
|
||||||
|
for target_file in WalkDir::new(song_dir)
|
||||||
|
.follow_links(true)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
{
|
||||||
|
if target_file.depth() >= 3 { // Don't recurse very deep
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = target_file.path();
|
||||||
|
if !path.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let format = FileFormat::from_file(&path)?.kind();
|
||||||
|
if format != Kind::Image {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let image_uri = URI::Local(path.to_path_buf().canonicalize().unwrap());
|
||||||
|
|
||||||
|
images.push(AlbumArt::External(image_uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(images)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue