Optimised the album art display

This commit is contained in:
MrDulfin 2024-12-29 00:57:51 -05:00
parent 280a17788b
commit e1a0ffbd05
5 changed files with 949 additions and 1015 deletions

View file

@ -1,18 +1,17 @@
pub mod music_storage { pub mod music_storage {
pub mod library; pub mod library;
pub mod music_collection; pub mod music_collection;
pub mod playlist; pub mod playlist;
mod utils; mod utils;
#[allow(dead_code)] #[allow(dead_code)]
pub mod db_reader; pub mod db_reader;
} }
pub mod music_controller { pub mod music_controller {
pub mod connections; pub mod controller;
pub mod controller; pub mod controller_handle;
pub mod controller_handle; pub mod queue;
pub mod queue; }
}
pub mod config;
pub mod config;

View file

@ -1,100 +0,0 @@
// use std::{
// sync::{Arc, RwLock},
// error::Error,
// };
// use discord_rpc_client::Client;
// use listenbrainz::ListenBrainz;
// use uuid::Uuid;
// use crate::{
// config::config::Config, music_controller::controller::{Controller, QueueCmd, QueueResponse}, music_storage::library::{MusicLibrary, Song, Tag}
// };
// use super::controller::DatabaseResponse;
// impl Controller {
// pub fn listenbrainz_authenticate(&mut self) -> Result<ListenBrainz, Box<dyn Error>> {
// let config = &self.config.read().unwrap();
// let mut client = ListenBrainz::new();
// let lbz_token = match &config.connections.listenbrainz_token {
// Some(token) => token,
// None => todo!("No ListenBrainz token in config")
// };
// if !client.is_authenticated() {
// client.authenticate(lbz_token)?;
// }
// Ok(client)
// }
// pub fn lbz_scrobble(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> {
// let config = &self.config.read().unwrap();
// &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid));
// let res = &self.db_mail.recv()?;
// let song = match res {
// DatabaseResponse::Song(song) => song,
// _ => todo!()
// };
// let unknown = &"unknown".to_string();
// let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown);
// let track = song.get_tag(&Tag::Title).unwrap_or(unknown);
// let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str());
// client.listen(artist, track, release)?;
// Ok(())
// }
// pub fn lbz_now_playing(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> {
// let config = &self.config.read().unwrap();
// &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid));
// let res = &self.db_mail.recv()?;
// let song = match res {
// DatabaseResponse::Song(song) => song,
// _ => todo!()
// };
// let unknown = &"unknown".to_string();
// let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown);
// let track = song.get_tag(&Tag::Title).unwrap_or(unknown);
// let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str());
// client.listen(artist, track, release)?;
// Ok(())
// }
// pub fn discord_song_change(client: &mut Client,song: Song) {
// client.set_activity(|a| {
// a.state(format!("Listening to {}", song.get_tag(&Tag::Title).unwrap()))
// .into()
// });
// }
// }
// #[cfg(test)]
// mod test_super {
// use std::{thread::sleep, time::Duration};
// use super::*;
// use crate::config::config::tests::read_config_lib;
// #[test]
// fn listenbrainz() {
// let mut c = Controller::start(".\\test-config\\config_test.json").unwrap();
// let client = c.listenbrainz_authenticate().unwrap();
// c.q_new().unwrap();
// c.queue_mail[0].send(QueueCmd::SetVolume(0.04)).unwrap();
// let songs = c.lib_get_songs();
// c.q_enqueue(0, songs[1].location.to_owned()).unwrap();
// c.q_play(0).unwrap();
// sleep(Duration::from_secs(100));
// c.lbz_scrobble(client, songs[1].uuid).unwrap();
// }
// }

File diff suppressed because it is too large Load diff

View file

@ -1,57 +1,59 @@
use std::{fs::OpenOptions, io::{Read, Write}}; use std::{fs::OpenOptions, io::{Read, Write}};
use dmp_core::music_controller::{controller::{ControllerHandle, LibraryResponse, PlayerCommand, PlayerLocation, PlayerResponse, QueueResponse}, queue::QueueSong}; use dmp_core::music_controller::{controller::{ControllerHandle, LibraryResponse, PlayerCommand, PlayerLocation, PlayerResponse, QueueResponse}, queue::QueueSong};
use kushi::QueueItem; use kushi::QueueItem;
use tauri::{AppHandle, Emitter, State, Wry}; use tauri::{AppHandle, Emitter, State, Wry};
use tempfile::TempDir; use tempfile::TempDir;
use uuid::Uuid; use uuid::Uuid;
use crate::wrappers::_Song; use crate::wrappers::_Song;
#[tauri::command] #[tauri::command]
pub async fn add_song_to_queue(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> { pub async fn add_song_to_queue(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> {
dbg!(&location); dbg!(&location);
let (song, _) = ctrl_handle.lib_get_song(uuid).await; let (song, _) = ctrl_handle.lib_get_song(uuid).await;
match ctrl_handle.queue_append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location }))).await { match ctrl_handle.queue_append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location }))).await {
Ok(()) => (), Ok(()) => (),
Err(e) => return Err(e.to_string()) Err(e) => return Err(e.to_string())
} }
app.emit("queue_updated", ()).unwrap(); app.emit("queue_updated", ()).unwrap();
Ok(()) Ok(())
} }
#[tauri::command] #[tauri::command]
pub async fn play_now(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> { pub async fn play_now(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> {
let song = match ctrl_handle.play_now(uuid, location).await { let song = match ctrl_handle.play_now(uuid, location).await {
Ok(song) => song, Ok(song) => song,
Err(e) => return Err(e.to_string()) Err(e) => return Err(e.to_string())
}; };
app.emit("queue_updated", ()).unwrap(); app.emit("queue_updated", ()).unwrap();
app.emit("now_playing_change", _Song::from(&song)).unwrap(); app.emit("now_playing_change", _Song::from(&song)).unwrap();
app.emit("playing", ()).unwrap(); app.emit("playing", ()).unwrap();
Ok(()) Ok(())
} }
#[tauri::command] #[tauri::command]
pub async fn display_album_art(ctrl_handle: State<'_, ControllerHandle>, temp_dir: State<'_, TempDir>, uuid: Uuid) -> Result<(), String> { pub async fn display_album_art(ctrl_handle: State<'_, ControllerHandle>, temp_dir: State<'_, TempDir>, uuid: Uuid) -> Result<(), String> {
match ctrl_handle.lib_get_song(uuid.clone()).await.0.album_art(0) { match ctrl_handle.lib_get_song(uuid.clone()).await.0.album_art(0) {
Ok(art) => { Ok(art) => {
let mut art = art.unwrap(); let mut art = art.unwrap();
let path = temp_dir.path().join(format!("CoverArt_{uuid}.{}", file_format::FileFormat::from_bytes(&art).extension())); let path = temp_dir.path().join(format!("CoverArt_{uuid}.{}", file_format::FileFormat::from_bytes(&art).extension()));
// TODO: This can be optimised later if !path.exists() {
let mut file = OpenOptions::new() // TODO: This can be optimised later
.create(true) let mut file = OpenOptions::new()
.truncate(true) .create(true)
.write(true) .truncate(true)
.read(true) .write(true)
.open(path.clone()) .read(true)
.unwrap(); .open(path.clone())
file.write_all(&mut art).unwrap(); .unwrap();
opener::open(path).unwrap(); file.write_all(&mut art).unwrap();
} }
Err(e) => return Err(e.to_string()) opener::open(path).unwrap();
}; }
Ok(()) Err(e) => return Err(e.to_string())
};
Ok(())
} }

View file

@ -1,211 +1,221 @@
use std::{fs, path::PathBuf, str::FromStr, thread::spawn}; use std::{fs, path::PathBuf, str::FromStr, thread::{scope, spawn}};
use crossbeam::channel::{unbounded, Receiver, Sender}; use crossbeam::channel::{unbounded, Receiver, Sender};
use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{Controller, ControllerHandle, LibraryResponse}, music_storage::library::MusicLibrary}; use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{Controller, ControllerHandle, LibraryResponse, PlaybackInfo}, music_storage::library::MusicLibrary};
use rfd::FileHandle; use rfd::FileHandle;
use tauri::{http::Response, Emitter, Manager, State, WebviewWindowBuilder, Wry}; use tauri::{http::Response, Emitter, Manager, State, WebviewWindowBuilder, Wry};
use uuid::Uuid; use uuid::Uuid;
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue, import_playlist, get_playlist, get_playlists, remove_from_queue}; use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue, import_playlist, get_playlist, get_playlists, remove_from_queue};
use commands::{add_song_to_queue, play_now, display_album_art}; use commands::{add_song_to_queue, play_now, display_album_art};
pub mod wrappers; pub mod wrappers;
pub mod commands; pub mod commands;
const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png"); const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png");
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
let (rx, tx) = unbounded::<Config>(); let (rx, tx) = unbounded::<Config>();
let (lib_rx, lib_tx) = unbounded::<Option<PathBuf>>(); let (lib_rx, lib_tx) = unbounded::<Option<PathBuf>>();
let (handle_rx, handle_tx) = unbounded::<ControllerHandle>(); let (handle_rx, handle_tx) = unbounded::<ControllerHandle>();
let (playback_handle_rx, playback_handle_tx) = unbounded::<crossbeam::channel::Receiver<PlaybackInfo>>();
let controller_thread = spawn(move || {
let mut config = { tx.recv().unwrap() } ; let controller_thread = spawn(move || {
let scan_path = { lib_tx.recv().unwrap() }; let mut config = { tx.recv().unwrap() } ;
let _temp_config = ConfigLibrary::default(); let scan_path = { lib_tx.recv().unwrap() };
let _lib = config.libraries.get_default().unwrap_or(&_temp_config); let _temp_config = ConfigLibrary::default();
let _lib = config.libraries.get_default().unwrap_or(&_temp_config);
let save_path = if _lib.path == PathBuf::default() {
scan_path.as_ref().unwrap().clone().canonicalize().unwrap().join("library.dlib") let save_path = if _lib.path == PathBuf::default() {
} else { scan_path.as_ref().unwrap().clone().canonicalize().unwrap().join("library.dlib")
_lib.path.clone() } else {
}; _lib.path.clone()
println!("save_path: {}\nscan_path:{scan_path:?}", save_path.display()); };
println!("save_path: {}\nscan_path:{scan_path:?}", save_path.display());
let mut library = MusicLibrary::init(
save_path.clone(), let mut library = MusicLibrary::init(
_lib.uuid save_path.clone(),
).unwrap(); _lib.uuid
).unwrap();
let scan_path = scan_path.unwrap_or_else(|| config.libraries.get_default().unwrap().scan_folders.as_ref().unwrap()[0].clone());
let scan_path = scan_path.unwrap_or_else(|| config.libraries.get_default().unwrap().scan_folders.as_ref().unwrap()[0].clone());
if config.libraries.get_default().is_err() {
library.scan_folder(&scan_path).unwrap(); if config.libraries.get_default().is_err() {
config.push_library( ConfigLibrary::new(save_path.clone(), String::from("Library"), Some(vec![scan_path.clone()]), Some(library.uuid))); library.scan_folder(&scan_path).unwrap();
} config.push_library( ConfigLibrary::new(save_path.clone(), String::from("Library"), Some(vec![scan_path.clone()]), Some(library.uuid)));
if library.library.is_empty() { }
println!("library is empty"); if library.library.is_empty() {
} else { println!("library is empty");
config.write_file().unwrap(); } else {
} config.write_file().unwrap();
println!("scan_path: {}", scan_path.display()); }
println!("scan_path: {}", scan_path.display());
library.save(save_path).unwrap();
library.save(save_path).unwrap();
let (handle, input) = ControllerHandle::new(
library, let (handle, input, playback_tx) = ControllerHandle::new(
std::sync::Arc::new(std::sync::RwLock::new(config)) library,
); std::sync::Arc::new(std::sync::RwLock::new(config))
);
handle_rx.send(handle).unwrap();
handle_rx.send(handle).unwrap();
let _controller = futures::executor::block_on(Controller::start(input)).unwrap();
}); scope(|s| {
let app = tauri::Builder::default() s.spawn(|| {
.plugin(tauri_plugin_shell::init()) let _controller = futures::executor::block_on(Controller::start(input)).unwrap();
.invoke_handler(tauri::generate_handler![ });
get_config, s.spawn(|| {
create_new_library,
get_library, });
play, })
pause,
set_volume,
next, });
prev, let app = tauri::Builder::default()
get_song, .plugin(tauri_plugin_shell::init())
lib_already_created, .invoke_handler(tauri::generate_handler![
get_queue, get_config,
add_song_to_queue, create_new_library,
play_now, get_library,
import_playlist, play,
get_playlist, pause,
get_playlists, set_volume,
remove_from_queue, next,
display_album_art, prev,
]).manage(ConfigRx(rx)) get_song,
.manage(LibRx(lib_rx)) lib_already_created,
.manage(HandleTx(handle_tx)) get_queue,
.manage(tempfile::TempDir::new().unwrap()) add_song_to_queue,
.register_asynchronous_uri_scheme_protocol("asset", move |ctx, req, res| { play_now,
let query = req import_playlist,
.clone() get_playlist,
.uri() get_playlists,
.clone() remove_from_queue,
.into_parts() display_album_art,
.path_and_query ]).manage(ConfigRx(rx))
.unwrap() .manage(LibRx(lib_rx))
.query() .manage(HandleTx(handle_tx))
.unwrap() .manage(tempfile::TempDir::new().unwrap())
.to_string(); .register_asynchronous_uri_scheme_protocol("asset", move |ctx, req, res| {
let query = req
let bytes = if query.as_str() == "default" { .clone()
Some(DEFAULT_IMAGE.to_vec()) .uri()
} else {futures::executor::block_on(async move { .clone()
let controller = ctx.app_handle().state::<ControllerHandle>(); .into_parts()
controller.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(Uuid::parse_str(query.as_str()).unwrap())).await.unwrap(); .path_and_query
let LibraryResponse::Song(song, _) = controller.lib_mail.recv().await.unwrap() else { .unwrap()
return None .query()
}; .unwrap()
Some(song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec())) .to_string();
})};
let bytes = if query.as_str() == "default" {
res.respond( Some(DEFAULT_IMAGE.to_vec())
Response::builder() } else {futures::executor::block_on(async move {
.header("Origin", "*") let controller = ctx.app_handle().state::<ControllerHandle>();
.header("Content-Length", bytes.as_ref().unwrap_or(&vec![]).len()) controller.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(Uuid::parse_str(query.as_str()).unwrap())).await.unwrap();
.status(200) let LibraryResponse::Song(song, _) = controller.lib_mail.recv().await.unwrap() else {
.body(bytes.unwrap_or_default()) return None
.unwrap() };
); Some(song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec()))
}) })};
.build(tauri::generate_context!())
.expect("error while building tauri application"); res.respond(
Response::builder()
app .header("Origin", "*")
.run(|_app_handle, event| match event { .header("Content-Length", bytes.as_ref().unwrap_or(&vec![]).len())
tauri::RunEvent::ExitRequested { api, .. } => { .status(200)
// api.prevent_exit(); .body(bytes.unwrap_or_default())
panic!("does this kill the player?") .unwrap()
} );
_ => {} })
}); .build(tauri::generate_context!())
} .expect("error while building tauri application");
struct ConfigRx(Sender<Config>); app
.run(|_app_handle, event| match event {
struct LibRx(Sender<Option<PathBuf>>); tauri::RunEvent::ExitRequested { api, .. } => {
struct HandleTx(Receiver<ControllerHandle>); // api.prevent_exit();
panic!("does this kill the player?")
}
#[tauri::command] _ => {}
async fn get_config(state: State<'_, ConfigRx>) -> Result<Config, String> { });
if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") { }
let path = dir.config_dir();
fs::create_dir_all(path).or_else(|err| { struct ConfigRx(Sender<Config>);
if err.kind() == std::io::ErrorKind::AlreadyExists {
Ok(()) struct LibRx(Sender<Option<PathBuf>>);
} else { struct HandleTx(Receiver<ControllerHandle>);
Err(err)
}
}).unwrap(); #[tauri::command]
async fn get_config(state: State<'_, ConfigRx>) -> Result<Config, String> {
let config = if let Ok(mut c) = Config::read_file(PathBuf::from(path).join("config")) { if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") {
if c.state_path == PathBuf::default() { let path = dir.config_dir();
c.state_path = PathBuf::from(path).join("state"); fs::create_dir_all(path).or_else(|err| {
} if err.kind() == std::io::ErrorKind::AlreadyExists {
c Ok(())
} else { } else {
let c = Config { Err(err)
path: PathBuf::from(path).join("config"), }
state_path: PathBuf::from(path).join("state"), }).unwrap();
..Default::default()
}; let config = if let Ok(mut c) = Config::read_file(PathBuf::from(path).join("config")) {
c.write_file().unwrap(); if c.state_path == PathBuf::default() {
c c.state_path = PathBuf::from(path).join("state");
}; }
c
state.inner().0.send(config.clone()).unwrap(); } else {
let c = Config {
Ok(config) path: PathBuf::from(path).join("config"),
} else { state_path: PathBuf::from(path).join("state"),
panic!("No config dir for DMP") ..Default::default()
} };
} c.write_file().unwrap();
c
#[tauri::command] };
async fn create_new_library(
app: tauri::AppHandle<Wry>, state.inner().0.send(config.clone()).unwrap();
lib_rx: State<'_, LibRx>,
handle_tx: State<'_, HandleTx>, Ok(config)
) -> Result<(), String> { } else {
let dir = rfd::AsyncFileDialog::new() panic!("No config dir for DMP")
.set_title("Pick a library path") }
.pick_folder() }
.await
.unwrap(); #[tauri::command]
async fn create_new_library(
let path = dir.path().canonicalize().unwrap(); app: tauri::AppHandle<Wry>,
println!("{}", path.display()); lib_rx: State<'_, LibRx>,
handle_tx: State<'_, HandleTx>,
if !path.exists() { ) -> Result<(), String> {
panic!("Path {} does not exist!", path.display()) let dir = rfd::AsyncFileDialog::new()
} else if !path.is_dir() { .set_title("Pick a library path")
panic!("Path {} is not a directory!", path.display()) .pick_folder()
} .await
.unwrap();
lib_rx.inner().0.send(Some(path)).unwrap();
app.manage(handle_tx.inner().0.recv().unwrap()); let path = dir.path().canonicalize().unwrap();
app.emit("library_loaded", ()).unwrap(); println!("{}", path.display());
Ok(())
} if !path.exists() {
panic!("Path {} does not exist!", path.display())
#[tauri::command] } else if !path.is_dir() {
async fn lib_already_created(app: tauri::AppHandle<Wry>, lib_rx: State<'_, LibRx>, handle_tx: State<'_, HandleTx>) -> Result<(), String> { panic!("Path {} is not a directory!", path.display())
println!("lib already created"); }
lib_rx.inner().0.send(None).unwrap();
app.manage(handle_tx.inner().0.recv().unwrap()); lib_rx.inner().0.send(Some(path)).unwrap();
app.emit("library_loaded", ()).unwrap(); app.manage(handle_tx.inner().0.recv().unwrap());
Ok(()) app.emit("library_loaded", ()).unwrap();
} Ok(())
}
#[tauri::command]
async fn lib_already_created(app: tauri::AppHandle<Wry>, lib_rx: State<'_, LibRx>, handle_tx: State<'_, HandleTx>) -> Result<(), String> {
println!("lib already created");
lib_rx.inner().0.send(None).unwrap();
app.manage(handle_tx.inner().0.recv().unwrap());
app.emit("library_loaded", ()).unwrap();
Ok(())
}