added out_queue function, rudamentary listenbrainz scrobbling and other small changes

This commit is contained in:
MrDulfin 2024-04-05 20:26:13 -04:00
parent a75081d4fc
commit ab529f4c9b
6 changed files with 244 additions and 54 deletions

View file

@ -37,3 +37,4 @@ serde_json = "1.0.111"
deunicode = "1.4.2" deunicode = "1.4.2"
opener = { version = "0.7.0", features = ["reveal"] } opener = { version = "0.7.0", features = ["reveal"] }
tempfile = "3.10.1" tempfile = "3.10.1"
listenbrainz = "0.7.0"

View file

@ -90,11 +90,18 @@ impl ConfigLibraries {
} }
#[derive(Debug, Default, Serialize, Deserialize, Clone)] #[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct ConfigConnections {
pub listenbrainz_token: Option<String>
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Config { pub struct Config {
pub path: PathBuf, pub path: PathBuf,
pub backup_folder: Option<PathBuf>, pub backup_folder: Option<PathBuf>,
pub libraries: ConfigLibraries, pub libraries: ConfigLibraries,
pub volume: f32, pub volume: f32,
pub connections: ConfigConnections,
} }
impl Config { impl Config {
@ -212,10 +219,10 @@ pub mod tests {
#[test] #[test]
fn test3() { fn test3() {
let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap(); let (config, lib) = read_config_lib();
let uuid = config.libraries.get_default().unwrap().uuid;
let lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
dbg!(lib); _ = config.write_file();
dbg!(config);
} }
} }

View file

@ -1,6 +1,95 @@
use super::controller::Controller; use std::{
sync::{Arc, RwLock},
error::Error,
};
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 { impl Controller {
//more stuff goes here 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(())
}
}
#[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();
}
} }

View file

@ -6,12 +6,14 @@ use std::path::PathBuf;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use crossbeam_channel::{Sender, Receiver}; use crossbeam_channel::{Sender, Receiver};
use crossbeam_channel; use crossbeam_channel;
use listenbrainz::ListenBrainz;
use std::thread::spawn; use std::thread::spawn;
use std::error::Error; use std::error::Error;
use crossbeam_channel::unbounded; use crossbeam_channel::unbounded;
use uuid::Uuid; use uuid::Uuid;
use crate::music_controller::queue::{QueueItem, QueueItemType};
use crate::music_storage::library::{Tag, URI}; use crate::music_storage::library::{Tag, URI};
use crate::{ use crate::{
music_storage::library::{MusicLibrary, Song}, music_storage::library::{MusicLibrary, Song},
@ -21,27 +23,27 @@ use crate::{
pub struct Controller { pub struct Controller {
// queues: Vec<Queue>, // queues: Vec<Queue>,
config: Arc<RwLock<Config>>, pub config: Arc<RwLock<Config>>,
// library: MusicLibrary, // library: MusicLibrary,
controller_mail: MailMan<ControllerCmd, ControllerResponse>, pub(super) controller_mail: MailMan<ControllerCmd, ControllerResponse>,
db_mail: MailMan<DatabaseCmd, DatabaseResponse>, pub(super) db_mail: MailMan<DatabaseCmd, DatabaseResponse>,
queue_mail: Vec<MailMan<QueueCmd, QueueResponse>>, pub(super) queue_mail: Vec<MailMan<QueueCmd, QueueResponse>>,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ControllerCmd { pub(super) enum ControllerCmd {
Default, Default,
Test Test
} }
#[derive(Debug)] #[derive(Debug)]
enum ControllerResponse { pub(super) enum ControllerResponse {
Empty, Empty,
QueueMailMan(MailMan<QueueCmd, QueueResponse>), QueueMailMan(MailMan<QueueCmd, QueueResponse>),
} }
#[derive(Debug)] #[derive(Debug)]
pub enum DatabaseCmd { pub(super) enum DatabaseCmd {
Default, Default,
Test, Test,
SaveLibrary, SaveLibrary,
@ -49,11 +51,10 @@ pub enum DatabaseCmd {
QueryUuid(Uuid), QueryUuid(Uuid),
QueryUuids(Vec<Uuid>), QueryUuids(Vec<Uuid>),
ReadFolder(String), ReadFolder(String),
} }
#[derive(Debug)] #[derive(Debug)]
enum DatabaseResponse { pub(super) enum DatabaseResponse {
Empty, Empty,
Song(Song), Song(Song),
Songs(Vec<Song>), Songs(Vec<Song>),
@ -61,7 +62,7 @@ enum DatabaseResponse {
} }
#[derive(Debug)] #[derive(Debug)]
enum QueueCmd { pub(super) enum QueueCmd {
Default, Default,
Test, Test,
Play, Play,
@ -73,25 +74,26 @@ enum QueueCmd {
} }
#[derive(Debug)] #[derive(Debug)]
enum QueueResponse { pub(super) enum QueueResponse {
Default, Default,
Test, Test,
Index(i32), Index(i32),
Uuid(Uuid),
} }
#[derive(Debug)] #[derive(Debug)]
struct MailMan<T, U> { pub(super) struct MailMan<T: Send, U: Send> {
pub tx: Sender<T>, pub tx: Sender<T>,
rx: Receiver<U> rx: Receiver<U>
} }
impl<T> MailMan<T, T> { impl<T: Send> MailMan<T, T> {
pub fn new() -> Self { pub fn new() -> Self {
let (tx, rx) = unbounded::<T>(); let (tx, rx) = unbounded::<T>();
MailMan { tx, rx } MailMan { tx, rx }
} }
} }
impl<T, U> MailMan<T, U> { impl<T: Send, U: Send> MailMan<T, U> {
pub fn double() -> (MailMan<T, U>, MailMan<U, T>) { pub fn double() -> (MailMan<T, U>, MailMan<U, T>) {
let (tx, rx) = unbounded::<T>(); let (tx, rx) = unbounded::<T>();
let (tx1, rx1) = unbounded::<U>(); let (tx1, rx1) = unbounded::<U>();
@ -108,14 +110,16 @@ impl<T, U> MailMan<T, U> {
} }
pub fn recv(&self) -> Result<U, Box<dyn Error>> { pub fn recv(&self) -> Result<U, Box<dyn Error>> {
let u = self.rx.recv().unwrap(); let u = self.rx.recv()?;
Ok(u) Ok(u)
} }
} }
#[allow(unused_variables)] #[allow(unused_variables)]
impl Controller { impl Controller {
pub fn start(config_path: String) -> Result<Self, Box<dyn Error>> { pub fn start<P>(config_path: P) -> Result<Self, Box<dyn Error>>
where std::path::PathBuf: std::convert::From<P>
{
let config_path = PathBuf::from(config_path); let config_path = PathBuf::from(config_path);
let config = Config::read_file(config_path)?; let config = Config::read_file(config_path)?;
let uuid = config.libraries.get_default()?.uuid; let uuid = config.libraries.get_default()?.uuid;
@ -185,7 +189,6 @@ impl Controller {
}); });
Ok( Ok(
Controller { Controller {
// queues: Vec::new(), // queues: Vec::new(),
@ -197,7 +200,7 @@ impl Controller {
) )
} }
fn lib_get_songs(&self) -> Vec<Song> { pub fn lib_get_songs(&self) -> Vec<Song> {
self.db_mail.send(DatabaseCmd::GetSongs); self.db_mail.send(DatabaseCmd::GetSongs);
match self.db_mail.recv().unwrap() { match self.db_mail.recv().unwrap() {
DatabaseResponse::Songs(songs) => songs, DatabaseResponse::Songs(songs) => songs,
@ -205,7 +208,7 @@ impl Controller {
} }
} }
fn lib_scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> { pub fn lib_scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
let mail = &self.db_mail; let mail = &self.db_mail;
mail.send(DatabaseCmd::ReadFolder(folder))?; mail.send(DatabaseCmd::ReadFolder(folder))?;
dbg!(mail.recv()?); dbg!(mail.recv()?);
@ -259,14 +262,14 @@ impl Controller {
Ok(self.queue_mail.len() - 1) Ok(self.queue_mail.len() - 1)
} }
fn q_play(&self, index: usize) -> Result<(), Box<dyn Error>> { pub fn q_play(&self, index: usize) -> Result<(), Box<dyn Error>> {
let mail = &self.queue_mail[index]; let mail = &self.queue_mail[index];
mail.send(QueueCmd::Play)?; mail.send(QueueCmd::Play)?;
dbg!(mail.recv()?); dbg!(mail.recv()?);
Ok(()) Ok(())
} }
fn q_pause(&self, index: usize) -> Result<(), Box<dyn Error>> { pub fn q_pause(&self, index: usize) -> Result<(), Box<dyn Error>> {
let mail = &self.queue_mail[index]; let mail = &self.queue_mail[index];
mail.send(QueueCmd::Pause)?; mail.send(QueueCmd::Pause)?;
dbg!(mail.recv()?); dbg!(mail.recv()?);
@ -286,7 +289,7 @@ impl Controller {
// Ok(()) // Ok(())
// } // }
fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> { pub fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
let mail = &self.queue_mail[index]; let mail = &self.queue_mail[index];
mail.send(QueueCmd::Enqueue(uri))?; mail.send(QueueCmd::Enqueue(uri))?;
// dbg!(mail.recv()?); // dbg!(mail.recv()?);

View file

@ -89,10 +89,10 @@ pub enum PlayerLocation {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[non_exhaustive] #[non_exhaustive]
pub struct QueueItem<'a> { pub struct QueueItem<'a> {
item: QueueItemType<'a>, pub(super) item: QueueItemType<'a>,
state: QueueState, pub(super) state: QueueState,
source: PlayerLocation, pub(super) source: PlayerLocation,
by_human: bool pub(super) by_human: bool
} }
impl QueueItem<'_> { impl QueueItem<'_> {
fn new() -> Self { fn new() -> Self {

View file

@ -1,18 +1,21 @@
use std::{fs::File, io::Read, path:: PathBuf, sync::{Arc, RwLock}}; use std::{fs::File, io::Read, path:: PathBuf, sync::{Arc, RwLock}};
use std::error::Error; use std::error::Error;
use chrono::Duration; use std::time::Duration;
use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI}; use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI};
use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2}; use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2};
#[derive(Debug, Clone)] use rayon::prelude::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SortOrder { pub enum SortOrder {
Manual, Manual,
Tag(Tag) Tag(Vec<Tag>)
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Playlist { pub struct Playlist {
uuid: Uuid, uuid: Uuid,
title: String, title: String,
@ -29,9 +32,24 @@ impl Playlist {
pub fn play_count(&self) -> i32 { pub fn play_count(&self) -> i32 {
self.play_count self.play_count
} }
pub fn play_time(&self) -> chrono::Duration { pub fn play_time(&self) -> Duration {
self.play_time self.play_time
} }
fn title(&self) -> &String {
&self.title
}
fn cover(&self) -> Option<&AlbumArt> {
match &self.cover {
Some(e) => Some(e),
None => None,
}
}
fn tracks(&self) -> Vec<Uuid> {
self.tracks.to_owned()
}
pub fn set_tracks(&mut self, tracks: Vec<Uuid>) { pub fn set_tracks(&mut self, tracks: Vec<Uuid>) {
self.tracks = tracks; self.tracks = tracks;
} }
@ -75,6 +93,15 @@ impl Playlist {
false false
} }
pub fn to_file(&self, path: &str) -> Result<(), Box<dyn Error>> {
super::utils::write_file(self, PathBuf::from(path))?;
Ok(())
}
pub fn from_file(path: &str) -> Result<Playlist, Box<dyn Error>> {
super::utils::read_file(PathBuf::from(path))
}
pub fn to_m3u8(&mut self, lib: Arc<RwLock<MusicLibrary>>, location: &str) -> Result<(), Box<dyn Error>> { pub fn to_m3u8(&mut self, lib: Arc<RwLock<MusicLibrary>>, location: &str) -> Result<(), Box<dyn Error>> {
let lib = lib.read().unwrap(); let lib = lib.read().unwrap();
let seg = self.tracks let seg = self.tracks
@ -174,22 +201,72 @@ impl Playlist {
} }
} }
} }
fn title(&self) -> &String {
&self.title
} pub fn out_tracks(&self, lib: Arc<RwLock<MusicLibrary>>) -> (Vec<Song>, Vec<&Uuid>) {
fn cover(&self) -> Option<&AlbumArt> { let lib = lib.read().unwrap();
match &self.cover { let mut songs = vec![];
Some(e) => Some(e), let mut invalid_uuids = vec![];
None => None,
for uuid in &self.tracks {
if let Some((track, _)) = lib.query_uuid(uuid) {
songs.push(track.to_owned());
}else {
invalid_uuids.push(uuid);
} }
} }
fn tracks(&self) -> Vec<Uuid> {
self.tracks.to_owned() if let SortOrder::Tag(sort_by) = &self.sort_order {
println!("sorting by: {:?}", sort_by);
songs.par_sort_by(|a, b| {
for (i, sort_option) in sort_by.iter().enumerate() {
dbg!(&i);
let tag_a = match sort_option {
Tag::Field(field_selection) => match a.get_field(field_selection.as_str()) {
Some(field_value) => field_value.to_string(),
None => continue,
},
_ => match a.get_tag(sort_option) {
Some(tag_value) => tag_value.to_owned(),
None => continue,
},
};
let tag_b = match sort_option {
Tag::Field(field_selection) => match b.get_field(field_selection) {
Some(field_value) => field_value.to_string(),
None => continue,
},
_ => match b.get_tag(sort_option) {
Some(tag_value) => tag_value.to_owned(),
None => continue,
},
};
dbg!(&i);
if let (Ok(num_a), Ok(num_b)) = (tag_a.parse::<i32>(), tag_b.parse::<i32>()) {
// If parsing succeeds, compare as numbers
return dbg!(num_a.cmp(&num_b));
} else {
// If parsing fails, compare as strings
return dbg!(tag_a.cmp(&tag_b));
}
}
// If all tags are equal, sort by Track number
let path_a = PathBuf::from(a.get_field("location").unwrap().to_string());
let path_b = PathBuf::from(b.get_field("location").unwrap().to_string());
path_a.file_name().cmp(&path_b.file_name())
})
}
(songs, invalid_uuids)
} }
} }
impl Default for Playlist { impl Default for Playlist {
fn default() -> Self { fn default() -> Self {
Playlist { Playlist {
@ -199,7 +276,7 @@ impl Default for Playlist {
tracks: Vec::default(), tracks: Vec::default(),
sort_order: SortOrder::Manual, sort_order: SortOrder::Manual,
play_count: 0, play_count: 0,
play_time: Duration::zero(), play_time: Duration::from_millis(0),
} }
} }
} }
@ -207,7 +284,7 @@ impl Default for Playlist {
#[cfg(test)] #[cfg(test)]
mod test_super { mod test_super {
use super::*; use super::*;
use crate::config::config::tests::read_config_lib; use crate::{config::config::tests::read_config_lib, music_storage::playlist};
#[test] #[test]
fn list_to_m3u8() { fn list_to_m3u8() {
@ -219,11 +296,24 @@ mod test_super {
_ = playlist.to_m3u8(Arc::new(RwLock::from(lib)), ".\\test-config\\playlists\\playlist.m3u8"); _ = playlist.to_m3u8(Arc::new(RwLock::from(lib)), ".\\test-config\\playlists\\playlist.m3u8");
} }
#[test]
fn m3u8_to_list() { fn m3u8_to_list() -> Playlist {
let (_, lib) = read_config_lib(); let (_, lib) = read_config_lib();
let arc = Arc::new(RwLock::from(lib)); let arc = Arc::new(RwLock::from(lib));
let playlist = Playlist::from_m3u8(".\\test-config\\playlists\\playlist.m3u8", arc).unwrap(); let playlist = Playlist::from_m3u8(".\\test-config\\playlists\\playlist.m3u8", arc).unwrap();
dbg!(playlist);
playlist.to_file(".\\test-config\\playlists\\playlist");
dbg!(playlist)
}
#[test]
fn out_queue_sort() {
let (_, lib) = read_config_lib();
let mut list = m3u8_to_list();
list.sort_order = SortOrder::Tag(vec![Tag::Album]);
let songs = &list.out_tracks(Arc::new(RwLock::from(lib)));
dbg!(songs);
} }
} }