From 12aaab9174b2e1a0b4ff6ff4bac81a50332353e1 Mon Sep 17 00:00:00 2001 From: G2-Games Date: Sun, 19 Nov 2023 19:22:11 -0600 Subject: [PATCH 1/3] Updated `lofty` to `0.17.0`, fixing several issues --- Cargo.toml | 2 +- src/music_storage/music_db.rs | 51 ++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d30fe73..cfde222 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ categories = ["multimedia::audio"] [dependencies] file-format = { version = "0.22.0", features = ["reader-asf", "reader-ebml", "reader-mp4", "reader-rm", "reader-txt", "reader-xml", "serde"] } -lofty = "0.16.1" +lofty = "0.17.0" serde = { version = "1.0.191", features = ["derive"] } time = "0.3.22" toml = "0.7.5" diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs index 3372323..093a7c1 100644 --- a/src/music_storage/music_db.rs +++ b/src/music_storage/music_db.rs @@ -11,7 +11,7 @@ use std::ops::ControlFlow::{Break, Continue}; use rcue::parser::parse_from_file; use file_format::{FileFormat, Kind}; use walkdir::WalkDir; -use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt}; +use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt, ParseOptions}; use std::fs; use std::path::{Path, PathBuf}; @@ -246,7 +246,7 @@ impl Album<'_> { } } -const BLOCKED_EXTENSIONS: [&str; 3] = ["vob", "log", "txt"]; +const BLOCKED_EXTENSIONS: [&str; 5] = ["vob", "log", "txt", "sf2", "mid"]; #[derive(Debug)] pub struct MusicLibrary { @@ -341,6 +341,7 @@ impl MusicLibrary { config: &Config, ) -> Result> { let mut total = 0; + let mut errors = 0; for target_file in WalkDir::new(target_path) .follow_links(true) .into_iter() @@ -381,6 +382,7 @@ impl MusicLibrary { match self.add_file(&target_file.path()) { Ok(_) => total += 1, Err(_error) => { + errors += 1; println!("{}, {:?}: {}", format, target_file.file_name(), _error) } // TODO: Handle more of these errors }; @@ -388,6 +390,7 @@ impl MusicLibrary { total += match self.add_cuesheet(&target_file.path().to_path_buf()) { Ok(added) => added, Err(error) => { + errors += 1; println!("{}", error); 0 } @@ -398,19 +401,19 @@ impl MusicLibrary { // Save the database after scanning finishes self.save(&config).unwrap(); + println!("ERRORS: {}", errors); + Ok(total) } pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box> { + let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed); + // TODO: Fix error handling here - let tagged_file = match lofty::read_from_path(target_file) { + let tagged_file = match Probe::open(target_file)?.options(normal_options).read() { Ok(tagged_file) => tagged_file, - Err(_) => match Probe::open(target_file)?.read() { - Ok(tagged_file) => tagged_file, - - Err(error) => return Err(error.into()), - }, + Err(error) => return Err(error.into()), }; // Ensure the tags exist, if not, insert blank data @@ -435,7 +438,7 @@ impl MusicLibrary { ItemKey::Comment => Tag::Comment, ItemKey::AlbumTitle => Tag::Album, ItemKey::DiscNumber => Tag::Disk, - ItemKey::Unknown(unknown) if unknown == "ACOUSTID_FINGERPRINT" => continue, + ItemKey::Unknown(unknown) if unknown == "ACOUSTID_FINGERPRINT" || unknown == "Acoustid Fingerprint" => continue, ItemKey::Unknown(unknown) => Tag::Key(unknown.to_string()), custom => Tag::Key(format!("{:?}", custom)), }; @@ -490,7 +493,9 @@ impl MusicLibrary { match self.add_song(new_song) { Ok(_) => (), - Err(error) => return Err(error), + Err(_) => { + //return Err(error) + }, }; Ok(()) @@ -532,7 +537,9 @@ impl MusicLibrary { None => Duration::from_secs(0), }; let mut start = track.indices[0].1; - start -= pregap; + if !start.is_zero() { + start -= pregap; + } let duration = match next_track.next() { Some(future) => match future.indices.get(0) { @@ -540,17 +547,15 @@ impl MusicLibrary { None => Duration::from_secs(0) } None => { - let tagged_file = match lofty::read_from_path(&audio_location) { - Ok(tagged_file) => tagged_file, + match lofty::read_from_path(&audio_location) { + Ok(tagged_file) => tagged_file.properties().duration() - start, Err(_) => match Probe::open(&audio_location)?.read() { - Ok(tagged_file) => tagged_file, + Ok(tagged_file) => tagged_file.properties().duration() - start, - Err(error) => return Err(error.into()), + Err(_) => Duration::from_secs(0), }, - }; - - tagged_file.properties().duration() - start + } } }; let end = start + duration + postgap; @@ -656,18 +661,22 @@ impl MusicLibrary { } /// Scan the song by a location and update its tags - pub fn update_by_file(&mut self, new_tags: Song) -> Result<(), Box> { - match self.query_uri(&new_tags.location) { + pub fn update_uri(&mut self, target_uri: &URI, new_tags: Vec) -> Result<(), Box> { + match self.query_uri(target_uri) { Some(_) => (), None => return Err(format!("URI not in database!").into()), } + for tag in new_tags { + println!("{:?}", tag); + } + todo!() } /// Query the database, returning a list of [Song]s /// - /// The order in which the sort by Vec is arranged + /// The order in which the `sort by` Vec is arranged /// determines the output sorting. /// /// Example: From ba4ed346ce018a7dd808ce4837aea8d95891edc2 Mon Sep 17 00:00:00 2001 From: G2-Games Date: Fri, 24 Nov 2023 14:22:20 -0600 Subject: [PATCH 2/3] Removed music_player in preparation for GStreamer based backend --- src/lib.rs | 7 - src/music_controller/init.rs | 14 - src/music_controller/music_controller.rs | 23 +- src/music_player/music_output.rs | 209 --------- src/music_player/music_player.rs | 529 ----------------------- src/music_player/music_resampler.rs | 154 ------- src/music_storage/playlist.rs | 2 +- src/music_tracker/music_tracker.rs | 8 +- 8 files changed, 6 insertions(+), 940 deletions(-) delete mode 100644 src/music_controller/init.rs delete mode 100644 src/music_player/music_output.rs delete mode 100644 src/music_player/music_player.rs delete mode 100644 src/music_player/music_resampler.rs diff --git a/src/lib.rs b/src/lib.rs index 7aa37eb..a40622d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,14 +8,7 @@ pub mod music_storage { mod utils; } -pub mod music_player { - pub mod music_output; - pub mod music_player; - pub mod music_resampler; -} - pub mod music_controller { pub mod config; - pub mod init; pub mod music_controller; } diff --git a/src/music_controller/init.rs b/src/music_controller/init.rs deleted file mode 100644 index 38b101f..0000000 --- a/src/music_controller/init.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::fs::File; -use std::path::Path; - -pub fn init() {} - -fn init_config() { - let config_path = "./config.toml"; - - if !Path::new(config_path).try_exists().unwrap() { - File::create("./config.toml").unwrap(); - } -} - -fn init_db() {} diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs index 51be432..8a86205 100644 --- a/src/music_controller/music_controller.rs +++ b/src/music_controller/music_controller.rs @@ -2,20 +2,17 @@ use std::path::PathBuf; use std::sync::{Arc, RwLock}; use crate::music_controller::config::Config; -use crate::music_player::music_player::{DecoderMessage, MusicPlayer, PlayerStatus}; use crate::music_storage::music_db::{MusicLibrary, Song, Tag}; pub struct MusicController { pub config: Arc>, pub library: MusicLibrary, - music_player: MusicPlayer, } impl MusicController { /// Creates new MusicController with config at given path pub fn new(config_path: &PathBuf) -> Result> { let config = Arc::new(RwLock::new(Config::new(config_path)?)); - let music_player = MusicPlayer::new(config.clone()); let library = match MusicLibrary::init(config.clone()) { Ok(library) => library, Err(error) => return Err(error), @@ -24,7 +21,6 @@ impl MusicController { let controller = MusicController { config, library, - music_player, }; return Ok(controller); @@ -33,7 +29,6 @@ impl MusicController { /// Creates new music controller from a config at given path pub fn from(config_path: &PathBuf) -> Result> { let config = Arc::new(RwLock::new(Config::from(config_path)?)); - let music_player = MusicPlayer::new(config.clone()); let library = match MusicLibrary::init(config.clone()) { Ok(library) => library, Err(error) => return Err(error), @@ -42,33 +37,17 @@ impl MusicController { let controller = MusicController { config, library, - music_player, }; return Ok(controller); } - /// Sends given message to control music player - pub fn song_control(&mut self, message: DecoderMessage) { - self.music_player.send_message(message); - } - - /// Gets status of the music player - pub fn player_status(&mut self) -> PlayerStatus { - return self.music_player.get_status(); - } - - /// Gets current song being controlled, if any - pub fn get_current_song(&self) -> Option { - return self.music_player.get_current_song(); - } - /// Queries the [MusicLibrary], returning a `Vec` pub fn query_library( &self, query_string: &String, target_tags: Vec, - search_location: bool, + _search_location: bool, sort_by: Vec, ) -> Option> { self.library diff --git a/src/music_player/music_output.rs b/src/music_player/music_output.rs deleted file mode 100644 index 76e6d3b..0000000 --- a/src/music_player/music_output.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::{result, thread}; - -use symphonia::core::audio::{AudioBufferRef, RawSample, SampleBuffer, SignalSpec}; -use symphonia::core::conv::{ConvertibleSample, FromSample, IntoSample}; -use symphonia::core::units::Duration; - -use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use cpal::{self, SizedSample}; - -use rb::*; - -use crate::music_player::music_resampler::Resampler; - -pub trait AudioStream { - fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()>; - fn flush(&mut self); -} - -#[derive(Debug)] -pub enum AudioOutputError { - OpenStreamError, - PlayStreamError, - StreamClosedError, -} - -pub type Result = result::Result; - -pub trait OutputSample: - SizedSample - + FromSample - + IntoSample - + cpal::Sample - + ConvertibleSample - + RawSample - + std::marker::Send - + 'static -{ -} - -pub struct AudioOutput -where - T: OutputSample, -{ - ring_buf_producer: rb::Producer, - sample_buf: SampleBuffer, - stream: cpal::Stream, - resampler: Option>, -} -impl OutputSample for i8 {} -impl OutputSample for i16 {} -impl OutputSample for i32 {} -//impl OutputSample for i64 {} -impl OutputSample for u8 {} -impl OutputSample for u16 {} -impl OutputSample for u32 {} -//impl OutputSample for u64 {} -impl OutputSample for f32 {} -impl OutputSample for f64 {} -//create a new trait with functions, then impl that somehow - -pub fn open_stream(spec: SignalSpec, duration: Duration) -> Result> { - let host = cpal::default_host(); - - // Uses default audio device - let device = match host.default_output_device() { - Some(device) => device, - _ => return Err(AudioOutputError::OpenStreamError), - }; - - let config = match device.default_output_config() { - Ok(config) => config, - Err(err) => return Err(AudioOutputError::OpenStreamError), - }; - - return match config.sample_format() { - cpal::SampleFormat::I8 => { - AudioOutput::::create_stream(spec, &device, &config.into(), duration) - } - cpal::SampleFormat::I16 => { - AudioOutput::::create_stream(spec, &device, &config.into(), duration) - } - cpal::SampleFormat::I32 => { - AudioOutput::::create_stream(spec, &device, &config.into(), duration) - } - //cpal::SampleFormat::I64 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - cpal::SampleFormat::U8 => { - AudioOutput::::create_stream(spec, &device, &config.into(), duration) - } - cpal::SampleFormat::U16 => { - AudioOutput::::create_stream(spec, &device, &config.into(), duration) - } - cpal::SampleFormat::U32 => { - AudioOutput::::create_stream(spec, &device, &config.into(), duration) - } - //cpal::SampleFormat::U64 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - cpal::SampleFormat::F32 => { - AudioOutput::::create_stream(spec, &device, &config.into(), duration) - } - cpal::SampleFormat::F64 => { - AudioOutput::::create_stream(spec, &device, &config.into(), duration) - } - _ => todo!(), - }; -} - -impl AudioOutput { - // Creates the stream (TODO: Merge w/open_stream?) - fn create_stream( - spec: SignalSpec, - device: &cpal::Device, - config: &cpal::StreamConfig, - duration: Duration, - ) -> Result> { - let num_channels = config.channels as usize; - - // Ring buffer is created with 200ms audio capacity - let ring_len = ((50 * config.sample_rate.0 as usize) / 1000) * num_channels; - let ring_buf = rb::SpscRb::new(ring_len); - - let ring_buf_producer = ring_buf.producer(); - let ring_buf_consumer = ring_buf.consumer(); - - let stream_result = device.build_output_stream( - config, - move |data: &mut [T], _: &cpal::OutputCallbackInfo| { - // Writes samples in the ring buffer to the audio output - let written = ring_buf_consumer.read(data).unwrap_or(0); - - // Mutes non-written samples - data[written..] - .iter_mut() - .for_each(|sample| *sample = T::MID); - }, - //TODO: Handle error here properly - move |err| println!("Yeah we erroring out here"), - None, - ); - - if let Err(err) = stream_result { - return Err(AudioOutputError::OpenStreamError); - } - - let stream = stream_result.unwrap(); - - //Start output stream - if let Err(err) = stream.play() { - return Err(AudioOutputError::PlayStreamError); - } - - let sample_buf = SampleBuffer::::new(duration, spec); - - let mut resampler = None; - if spec.rate != config.sample_rate.0 { - println!("Resampling enabled"); - resampler = Some(Resampler::new( - spec, - config.sample_rate.0 as usize, - duration, - )) - } - - Ok(Box::new(AudioOutput { - ring_buf_producer, - sample_buf, - stream, - resampler, - })) - } -} - -impl AudioStream for AudioOutput { - // Writes given samples to ring buffer - fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()> { - if decoded.frames() == 0 { - return Ok(()); - } - - let mut samples: &[T] = if let Some(resampler) = &mut self.resampler { - // Resamples if required - match resampler.resample(decoded) { - Some(resampled) => resampled, - None => return Ok(()), - } - } else { - self.sample_buf.copy_interleaved_ref(decoded); - self.sample_buf.samples() - }; - - // Write samples into ring buffer - while let Some(written) = self.ring_buf_producer.write_blocking(samples) { - samples = &samples[written..]; - } - - Ok(()) - } - - // Flushes resampler if needed - fn flush(&mut self) { - if let Some(resampler) = &mut self.resampler { - let mut stale_samples = resampler.flush().unwrap_or_default(); - - while let Some(written) = self.ring_buf_producer.write_blocking(stale_samples) { - stale_samples = &stale_samples[written..]; - } - } - - let _ = self.stream.pause(); - } -} diff --git a/src/music_player/music_player.rs b/src/music_player/music_player.rs deleted file mode 100644 index e3c43ad..0000000 --- a/src/music_player/music_player.rs +++ /dev/null @@ -1,529 +0,0 @@ -use std::io::SeekFrom; -use std::sync::mpsc::{self, Receiver, Sender}; -use std::sync::{Arc, RwLock}; -use std::thread; - -use async_std::io::ReadExt; -use async_std::task; - -use futures::future::join_all; -use symphonia::core::codecs::{Decoder, DecoderOptions, CODEC_TYPE_NULL}; -use symphonia::core::errors::Error; -use symphonia::core::formats::{FormatOptions, FormatReader, SeekMode, SeekTo}; -use symphonia::core::io::{MediaSource, MediaSourceStream}; -use symphonia::core::meta::MetadataOptions; -use symphonia::core::probe::Hint; -use symphonia::core::units::{Time, TimeBase}; - -use futures::AsyncBufRead; - -use crate::music_controller::config::Config; -use crate::music_player::music_output::AudioStream; -use crate::music_storage::music_db::{Song, URI}; -use crate::music_tracker::music_tracker::{ - DiscordRPC, LastFM, ListenBrainz, MusicTracker, TrackerError, -}; - -// Struct that controls playback of music -pub struct MusicPlayer { - player_status: PlayerStatus, - music_trackers: Vec>, - current_song: Arc>>, - message_sender: Sender, - status_receiver: Receiver, - config: Arc>, -} - -#[derive(Clone, Copy, Debug)] -pub enum PlayerStatus { - Playing(f64), - Paused, - Stopped, - Error, -} - -#[derive(Debug, Clone)] -pub enum DecoderMessage { - OpenSong(Song), - Play, - Pause, - Stop, - SeekTo(u64), -} - -#[derive(Clone)] -pub enum TrackerMessage { - Track(Song), - TrackNow(Song), -} - -// Holds a song decoder reader, etc -struct SongHandler { - pub reader: Box, - pub decoder: Box, - pub time_base: Option, - pub duration: Option, -} - -// TODO: actual error handling here -impl SongHandler { - pub fn new(uri: &URI) -> Result { - // Opens remote/local source and creates MediaSource for symphonia - let config = RemoteOptions { - media_buffer_len: 10000, - forward_buffer_len: 10000, - }; - let src: Box = match uri { - URI::Local(path) => match std::fs::File::open(path) { - Ok(file) => Box::new(file), - Err(_) => return Err(()), - }, - URI::Remote(_, location) => { - match RemoteSource::new(location.to_str().unwrap(), &config) { - Ok(remote_source) => Box::new(remote_source), - Err(_) => return Err(()), - } - } - _ => todo!(), - }; - - let mss = MediaSourceStream::new(src, Default::default()); - - // Use default metadata and format options - let meta_opts: MetadataOptions = Default::default(); - let fmt_opts: FormatOptions = Default::default(); - - let hint = Hint::new(); - - let probed = symphonia::default::get_probe() - .format(&hint, mss, &fmt_opts, &meta_opts) - .expect("Unsupported format"); - - let reader = probed.format; - - let track = reader - .tracks() - .iter() - .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) - .expect("no supported audio tracks"); - - let time_base = track.codec_params.time_base; - let duration = track.codec_params.n_frames; - - let dec_opts: DecoderOptions = Default::default(); - - let decoder = symphonia::default::get_codecs() - .make(&track.codec_params, &dec_opts) - .expect("unsupported codec"); - - return Ok(SongHandler { - reader, - decoder, - time_base, - duration, - }); - } -} - -impl MusicPlayer { - pub fn new(config: Arc>) -> Self { - // Creates mpsc channels to communicate with music player threads - let (message_sender, message_receiver) = mpsc::channel(); - let (status_sender, status_receiver) = mpsc::channel(); - let current_song = Arc::new(RwLock::new(None)); - - MusicPlayer::start_player( - message_receiver, - status_sender, - config.clone(), - current_song.clone(), - ); - - MusicPlayer { - music_trackers: Vec::new(), - player_status: PlayerStatus::Stopped, - current_song, - message_sender, - status_receiver, - config, - } - } - - fn start_tracker( - status_sender: Sender>, - tracker_receiver: Receiver, - config: Arc>, - ) { - thread::spawn(move || { - let global_config = &*config.read().unwrap(); - // Sets local config for trackers to detect changes - let local_config = global_config.clone(); - let mut trackers: Vec> = Vec::new(); - // Updates local trackers to the music controller config // TODO: refactor - let update_trackers = |trackers: &mut Vec>| { - if let Some(lastfm_config) = global_config.lastfm.clone() { - if lastfm_config.enabled { - trackers.push(Box::new(LastFM::new(&lastfm_config))); - } - } - if let Some(discord_config) = global_config.discord.clone() { - if discord_config.enabled { - trackers.push(Box::new(DiscordRPC::new(&discord_config))); - } - } - if let Some(listenbz_config) = global_config.listenbrainz.clone() { - if listenbz_config.enabled { - trackers.push(Box::new(ListenBrainz::new(&listenbz_config))); - } - } - }; - update_trackers(&mut trackers); - loop { - if let message = tracker_receiver.recv() { - if local_config != global_config { - update_trackers(&mut trackers); - } - - let mut results = Vec::new(); - task::block_on(async { - let mut futures = Vec::new(); - for tracker in trackers.iter_mut() { - match message.clone() { - Ok(TrackerMessage::Track(song)) => { - futures.push(tracker.track_song(song)) - } - Ok(TrackerMessage::TrackNow(song)) => { - futures.push(tracker.track_now(song)) - } - Err(_) => {} - } - } - results = join_all(futures).await; - }); - - for result in results { - status_sender.send(result).unwrap_or_default() - } - } - } - }); - } - - // Opens and plays song with given path in separate thread - fn start_player( - message_receiver: Receiver, - status_sender: Sender, - config: Arc>, - current_song: Arc>>, - ) { - // Creates thread that audio is decoded in - thread::spawn(move || { - let current_song = current_song; - - let mut song_handler = None; - - let mut seek_time: Option = None; - - let mut audio_output: Option> = None; - - let (tracker_sender, tracker_receiver): ( - Sender, - Receiver, - ) = mpsc::channel(); - let (tracker_status_sender, tracker_status_receiver): ( - Sender>, - Receiver>, - ) = mpsc::channel(); - - MusicPlayer::start_tracker(tracker_status_sender, tracker_receiver, config); - - let mut song_tracked = false; - let mut song_time = 0.0; - let mut paused = true; - 'main_decode: loop { - 'handle_message: loop { - let message = if paused { - // Pauses playback by blocking on waiting for new player messages - match message_receiver.recv() { - Ok(message) => Some(message), - Err(_) => None, - } - } else { - // Resumes playback by not blocking - match message_receiver.try_recv() { - Ok(message) => Some(message), - Err(_) => break 'handle_message, - } - }; - // Handles message received from MusicPlayer struct - match message { - Some(DecoderMessage::OpenSong(song)) => { - let song_uri = song.location.clone(); - match SongHandler::new(&song_uri) { - Ok(new_handler) => { - song_handler = Some(new_handler); - *current_song.write().unwrap() = Some(song); - paused = false; - song_tracked = false; - } - Err(_) => status_sender.send(PlayerStatus::Error).unwrap(), - } - } - Some(DecoderMessage::Play) => { - if song_handler.is_some() { - paused = false; - } - } - Some(DecoderMessage::Pause) => { - paused = true; - status_sender.send(PlayerStatus::Paused).unwrap(); - } - Some(DecoderMessage::SeekTo(time)) => seek_time = Some(time), - // Exits main decode loop and subsequently ends thread - Some(DecoderMessage::Stop) => { - status_sender.send(PlayerStatus::Stopped).unwrap(); - break 'main_decode; - } - None => {} - } - status_sender.send(PlayerStatus::Error).unwrap(); - } - // In theory this check should not need to occur? - if let (Some(song_handler), current_song) = - (&mut song_handler, &*current_song.read().unwrap()) - { - match seek_time { - Some(time) => { - let seek_to = SeekTo::Time { - time: Time::from(time), - track_id: Some(0), - }; - song_handler - .reader - .seek(SeekMode::Accurate, seek_to) - .unwrap(); - seek_time = None; - } - None => {} //Nothing to do! - } - let packet = match song_handler.reader.next_packet() { - Ok(packet) => packet, - Err(Error::ResetRequired) => panic!(), //TODO, - Err(err) => { - // Unrecoverable? - panic!("{}", err); - } - }; - - if let (Some(time_base), Some(song)) = (song_handler.time_base, current_song) { - let time_units = time_base.calc_time(packet.ts); - song_time = time_units.seconds as f64 + time_units.frac; - // Tracks song now if song has just started - if song_time == 0.0 { - tracker_sender - .send(TrackerMessage::TrackNow(song.clone())) - .unwrap(); - } - - if let Some(duration) = song_handler.duration { - let song_duration = time_base.calc_time(duration); - let song_duration_secs = - song_duration.seconds as f64 + song_duration.frac; - // Tracks song if current time is past half of total song duration or past 4 minutes - if (song_duration_secs / 2.0 < song_time || song_time > 240.0) - && !song_tracked - { - song_tracked = true; - tracker_sender - .send(TrackerMessage::Track(song.clone())) - .unwrap(); - } - } - } - - status_sender - .send(PlayerStatus::Playing(song_time)) - .unwrap(); - - match song_handler.decoder.decode(&packet) { - Ok(decoded) => { - // Opens audio stream if there is not one - if audio_output.is_none() { - let spec = *decoded.spec(); - let duration = decoded.capacity() as u64; - - audio_output.replace( - crate::music_player::music_output::open_stream(spec, duration) - .unwrap(), - ); - } - } - Err(Error::IoError(_)) => { - // rest in peace packet - continue; - } - Err(Error::DecodeError(_)) => { - // may you one day be decoded - continue; - } - Err(err) => { - // Unrecoverable, though shouldn't panic here - panic!("{}", err); - } - } - } - } - }); - } - - // Updates status by checking on messages from spawned thread - fn update_player(&mut self) { - for message in self.status_receiver.try_recv() { - self.player_status = message; - } - } - - pub fn get_current_song(&self) -> Option { - match self.current_song.try_read() { - Ok(song) => return (*song).clone(), - Err(_) => return None, - } - } - - // Sends message to spawned thread - pub fn send_message(&mut self, message: DecoderMessage) { - self.update_player(); - // Checks that message sender exists before sending a message off - self.message_sender.send(message).unwrap(); - } - - pub fn get_status(&mut self) -> PlayerStatus { - self.update_player(); - return self.player_status; - } -} - -// TODO: Make the buffer length do anything -/// Options for remote sources -/// -/// media_buffer_len is how many bytes are to be buffered in totala -/// -/// forward_buffer is how many bytes can ahead of the seek position without the remote source being read from -pub struct RemoteOptions { - media_buffer_len: u64, - forward_buffer_len: u64, -} - -impl Default for RemoteOptions { - fn default() -> Self { - RemoteOptions { - media_buffer_len: 100000, - forward_buffer_len: 1024, - } - } -} - -/// A remote source of media -struct RemoteSource { - reader: Box, - media_buffer: Vec, - forward_buffer_len: u64, - offset: u64, -} - -impl RemoteSource { - /// Creates a new RemoteSource with given uri and configuration - pub fn new(uri: &str, config: &RemoteOptions) -> Result { - let mut response = task::block_on(async { - return surf::get(uri).await; - })?; - - let reader = response.take_body().into_reader(); - - Ok(RemoteSource { - reader, - media_buffer: Vec::new(), - forward_buffer_len: config.forward_buffer_len, - offset: 0, - }) - } -} -// TODO: refactor this + buffer into the buffer passed into the function, not a newly allocated one -impl std::io::Read for RemoteSource { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - // Reads bytes into the media buffer if the offset is within the specified distance from the end of the buffer - if self.media_buffer.len() as u64 - self.offset < self.forward_buffer_len { - let mut buffer = [0; 1024]; - let read_bytes = task::block_on(async { - match self.reader.read_exact(&mut buffer).await { - Ok(_) => { - self.media_buffer.extend_from_slice(&buffer); - return Ok(()); - } - Err(err) => return Err(err), - } - }); - match read_bytes { - Err(err) => return Err(err), - _ => {} - } - } - // Reads bytes from the media buffer into the buffer given by - let mut bytes_read = 0; - for location in 0..1024 { - if (location + self.offset as usize) < self.media_buffer.len() { - buf[location] = self.media_buffer[location + self.offset as usize]; - bytes_read += 1; - } - } - - self.offset += bytes_read; - return Ok(bytes_read as usize); - } -} - -impl std::io::Seek for RemoteSource { - // Seeks to a given position - // Seeking past the internal buffer's length results in the seeking to the end of content - fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { - match pos { - // Offset is set to given position - SeekFrom::Start(pos) => { - if pos > self.media_buffer.len() as u64 { - self.offset = self.media_buffer.len() as u64; - } else { - self.offset = pos; - } - return Ok(self.offset); - } - // Offset is set to length of buffer + given position - SeekFrom::End(pos) => { - if self.media_buffer.len() as u64 + pos as u64 > self.media_buffer.len() as u64 { - self.offset = self.media_buffer.len() as u64; - } else { - self.offset = self.media_buffer.len() as u64 + pos as u64; - } - return Ok(self.offset); - } - // Offset is set to current offset + given position - SeekFrom::Current(pos) => { - if self.offset + pos as u64 > self.media_buffer.len() as u64 { - self.offset = self.media_buffer.len() as u64; - } else { - self.offset += pos as u64 - } - return Ok(self.offset); - } - } - } -} - -impl MediaSource for RemoteSource { - fn is_seekable(&self) -> bool { - return true; - } - - fn byte_len(&self) -> Option { - return None; - } -} diff --git a/src/music_player/music_resampler.rs b/src/music_player/music_resampler.rs deleted file mode 100644 index 4040835..0000000 --- a/src/music_player/music_resampler.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Symphonia -// Copyright (c) 2019-2022 The Project Symphonia Developers. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, SignalSpec}; -use symphonia::core::conv::{FromSample, IntoSample}; -use symphonia::core::sample::Sample; - -pub struct Resampler { - resampler: rubato::FftFixedIn, - input: Vec>, - output: Vec>, - interleaved: Vec, - duration: usize, -} - -impl Resampler -where - T: Sample + FromSample + IntoSample, -{ - fn resample_inner(&mut self) -> &[T] { - { - //let mut input = heapless::Vec::::new(); - let mut input: arrayvec::ArrayVec<&[f32], 32> = Default::default(); - - for channel in self.input.iter() { - input.push(&channel[..self.duration]); - } - - // Resample. - rubato::Resampler::process_into_buffer( - &mut self.resampler, - &input, - &mut self.output, - None, - ) - .unwrap(); - } - - // Remove consumed samples from the input buffer. - for channel in self.input.iter_mut() { - channel.drain(0..self.duration); - } - - // Interleave the planar samples from Rubato. - let num_channels = self.output.len(); - - self.interleaved - .resize(num_channels * self.output[0].len(), T::MID); - - for (i, frame) in self.interleaved.chunks_exact_mut(num_channels).enumerate() { - for (ch, s) in frame.iter_mut().enumerate() { - *s = self.output[ch][i].into_sample(); - } - } - - &self.interleaved - } -} - -impl Resampler -where - T: Sample + FromSample + IntoSample, -{ - pub fn new(spec: SignalSpec, to_sample_rate: usize, duration: u64) -> Self { - let duration = duration as usize; - let num_channels = spec.channels.count(); - - let resampler = rubato::FftFixedIn::::new( - spec.rate as usize, - to_sample_rate, - duration, - 2, - num_channels, - ) - .unwrap(); - - let output = rubato::Resampler::output_buffer_allocate(&resampler); - - let input = vec![Vec::with_capacity(duration); num_channels]; - - Self { - resampler, - input, - output, - duration, - interleaved: Default::default(), - } - } - - /// Resamples a planar/non-interleaved input. - /// - /// Returns the resampled samples in an interleaved format. - pub fn resample(&mut self, input: AudioBufferRef<'_>) -> Option<&[T]> { - // Copy and convert samples into input buffer. - convert_samples_any(&input, &mut self.input); - - // Check if more samples are required. - if self.input[0].len() < self.duration { - return None; - } - - Some(self.resample_inner()) - } - - /// Resample any remaining samples in the resample buffer. - pub fn flush(&mut self) -> Option<&[T]> { - let len = self.input[0].len(); - - if len == 0 { - return None; - } - - let partial_len = len % self.duration; - - if partial_len != 0 { - // Fill each input channel buffer with silence to the next multiple of the resampler - // duration. - for channel in self.input.iter_mut() { - channel.resize(len + (self.duration - partial_len), f32::MID); - } - } - - Some(self.resample_inner()) - } -} - -fn convert_samples_any(input: &AudioBufferRef<'_>, output: &mut [Vec]) { - match input { - AudioBufferRef::U8(input) => convert_samples(input, output), - AudioBufferRef::U16(input) => convert_samples(input, output), - AudioBufferRef::U24(input) => convert_samples(input, output), - AudioBufferRef::U32(input) => convert_samples(input, output), - AudioBufferRef::S8(input) => convert_samples(input, output), - AudioBufferRef::S16(input) => convert_samples(input, output), - AudioBufferRef::S24(input) => convert_samples(input, output), - AudioBufferRef::S32(input) => convert_samples(input, output), - AudioBufferRef::F32(input) => convert_samples(input, output), - AudioBufferRef::F64(input) => convert_samples(input, output), - } -} - -fn convert_samples(input: &AudioBuffer, output: &mut [Vec]) -where - S: Sample + IntoSample, -{ - for (c, dst) in output.iter_mut().enumerate() { - let src = input.chan(c); - dst.extend(src.iter().map(|&s| s.into_sample())); - } -} diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs index 797d0d2..189b9e8 100644 --- a/src/music_storage/playlist.rs +++ b/src/music_storage/playlist.rs @@ -1,6 +1,6 @@ use crate::music_controller::config::Config; use std::path::Path; -pub fn playlist_add(config: &Config, playlist_name: &str, song_paths: &Vec<&Path>) { +pub fn playlist_add(_config: &Config, _playlist_name: &str, _song_paths: &Vec<&Path>) { unimplemented!() } diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs index 9ffd73d..37a3142 100644 --- a/src/music_tracker/music_tracker.rs +++ b/src/music_tracker/music_tracker.rs @@ -123,7 +123,7 @@ impl MusicTracker for LastFM { }; } - async fn get_times_tracked(&mut self, song: &Song) -> Result { + async fn get_times_tracked(&mut self, _song: &Song) -> Result { todo!(); } } @@ -319,7 +319,7 @@ impl MusicTracker for DiscordRPC { } } - async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> { + async fn track_song(&mut self, _song: Song) -> Result<(), TrackerError> { return Ok(()); } @@ -327,7 +327,7 @@ impl MusicTracker for DiscordRPC { return Ok(()); } - async fn get_times_tracked(&mut self, song: &Song) -> Result { + async fn get_times_tracked(&mut self, _song: &Song) -> Result { return Ok(0); } } @@ -408,7 +408,7 @@ impl MusicTracker for ListenBrainz { async fn test_tracker(&mut self) -> Result<(), TrackerError> { todo!() } - async fn get_times_tracked(&mut self, song: &Song) -> Result { + async fn get_times_tracked(&mut self, _song: &Song) -> Result { todo!() } } From 048254150c8488b1cb5d627e42f65af6a34446ad Mon Sep 17 00:00:00 2001 From: G2-Games Date: Fri, 24 Nov 2023 14:25:31 -0600 Subject: [PATCH 3/3] Ran `cargo clippy` --- src/music_controller/config.rs | 8 ++--- src/music_controller/music_controller.rs | 4 +-- src/music_storage/music_db.rs | 38 +++++++++----------- src/music_storage/utils.rs | 6 ++-- src/music_tracker/music_tracker.rs | 44 ++++++++++++------------ 5 files changed, 48 insertions(+), 52 deletions(-) diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs index 82cbd22..f450c9d 100644 --- a/src/music_controller/config.rs +++ b/src/music_controller/config.rs @@ -18,7 +18,7 @@ impl Default for Config { fn default() -> Self { let path = PathBuf::from("./music_database"); - return Config { + Config { db_path: Box::new(path), lastfm: None, @@ -34,7 +34,7 @@ impl Default for Config { api_url: String::from("https://api.listenbrainz.org"), auth_token: String::from(""), }), - }; + } } } @@ -49,10 +49,10 @@ impl Config { /// Loads config from given file path pub fn from(config_file: &PathBuf) -> std::result::Result { - return toml::from_str( + toml::from_str( &read_to_string(config_file) .expect("Failed to initalize music config: File not found!"), - ); + ) } /// Saves config to given path diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs index 8a86205..6f4881a 100644 --- a/src/music_controller/music_controller.rs +++ b/src/music_controller/music_controller.rs @@ -23,7 +23,7 @@ impl MusicController { library, }; - return Ok(controller); + Ok(controller) } /// Creates new music controller from a config at given path @@ -39,7 +39,7 @@ impl MusicController { library, }; - return Ok(controller); + Ok(controller) } /// Queries the [MusicLibrary], returning a `Vec` diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs index 093a7c1..f9ef854 100644 --- a/src/music_storage/music_db.rs +++ b/src/music_storage/music_db.rs @@ -115,10 +115,7 @@ impl Song { "location" => Some(self.location.clone().path_string()), "plays" => Some(self.plays.clone().to_string()), "format" => match self.format { - Some(format) => match format.short_name() { - Some(short) => Some(short.to_string()), - None => None, - }, + Some(format) => format.short_name().map(|short| short.to_string()), None => None, }, _ => todo!(), // Other field types are not yet supported @@ -321,10 +318,9 @@ impl MusicLibrary { /// with matching `PathBuf`s fn query_path(&self, path: &PathBuf) -> Option> { let result: Arc>> = Arc::new(Mutex::new(Vec::new())); - let _ = self.library.par_iter().for_each(|track| { + self.library.par_iter().for_each(|track| { if path == track.location.path() { - result.clone().lock().unwrap().push(&track); - return; + result.clone().lock().unwrap().push(track); } }); if result.lock().unwrap().len() > 0 { @@ -368,7 +364,7 @@ impl MusicLibrary { } */ - let format = FileFormat::from_file(&path)?; + let format = FileFormat::from_file(path)?; let extension = match path.extension() { Some(ext) => ext.to_string_lossy().to_ascii_lowercase(), None => String::new(), @@ -379,7 +375,7 @@ impl MusicLibrary { if (format.kind() == Kind::Audio || format.kind() == Kind::Video) && !BLOCKED_EXTENSIONS.contains(&extension.as_str()) { - match self.add_file(&target_file.path()) { + match self.add_file(target_file.path()) { Ok(_) => total += 1, Err(_error) => { errors += 1; @@ -399,7 +395,7 @@ impl MusicLibrary { } // Save the database after scanning finishes - self.save(&config).unwrap(); + self.save(config).unwrap(); println!("ERRORS: {}", errors); @@ -455,7 +451,7 @@ impl MusicLibrary { // Get all the album artwork information from the file let mut album_art: Vec = Vec::new(); for (i, _art) in tag.pictures().iter().enumerate() { - let new_art = AlbumArt::Embedded(i as usize); + let new_art = AlbumArt::Embedded(i); album_art.push(new_art) } @@ -547,10 +543,10 @@ impl MusicLibrary { None => Duration::from_secs(0) } None => { - match lofty::read_from_path(&audio_location) { + match lofty::read_from_path(audio_location) { Ok(tagged_file) => tagged_file.properties().duration() - start, - Err(_) => match Probe::open(&audio_location)?.read() { + Err(_) => match Probe::open(audio_location)?.read() { Ok(tagged_file) => tagged_file.properties().duration() - start, Err(_) => Duration::from_secs(0), @@ -561,7 +557,7 @@ impl MusicLibrary { let end = start + duration + postgap; // Get the format as a string - let format: Option = match FileFormat::from_file(&audio_location) { + let format: Option = match FileFormat::from_file(audio_location) { Ok(fmt) => Some(fmt), Err(_) => None, }; @@ -637,7 +633,7 @@ impl MusicLibrary { None => (), } match new_song.location { - URI::Local(_) if self.query_path(&new_song.location.path()).is_some() => { + URI::Local(_) if self.query_path(new_song.location.path()).is_some() => { return Err(format!("Location exists for {:?}", new_song.location).into()) } _ => (), @@ -664,7 +660,7 @@ impl MusicLibrary { pub fn update_uri(&mut self, target_uri: &URI, new_tags: Vec) -> Result<(), Box> { match self.query_uri(target_uri) { Some(_) => (), - None => return Err(format!("URI not in database!").into()), + None => return Err("URI not in database!".to_string().into()), } for tag in new_tags { @@ -709,11 +705,11 @@ impl MusicLibrary { self.library.par_iter().for_each(|track| { for tag in target_tags { let track_result = match tag { - Tag::Field(target) => match track.get_field(&target) { + Tag::Field(target) => match track.get_field(target) { Some(value) => value, None => continue, }, - _ => match track.get_tag(&tag) { + _ => match track.get_tag(tag) { Some(value) => value.clone(), None => continue, }, @@ -749,7 +745,7 @@ impl MusicLibrary { Some(field_value) => field_value, None => continue, }, - _ => match a.get_tag(&sort_option) { + _ => match a.get_tag(sort_option) { Some(tag_value) => tag_value.to_owned(), None => continue, }, @@ -760,7 +756,7 @@ impl MusicLibrary { Some(field_value) => field_value, None => continue, }, - _ => match b.get_tag(&sort_option) { + _ => match b.get_tag(sort_option) { Some(tag_value) => tag_value.to_owned(), None => continue, }, @@ -782,7 +778,7 @@ impl MusicLibrary { path_a.file_name().cmp(&path_b.file_name()) }); - if new_songs.len() > 0 { + if !new_songs.is_empty() { Some(new_songs) } else { None diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs index 0847ae0..f4a7947 100644 --- a/src/music_storage/utils.rs +++ b/src/music_storage/utils.rs @@ -45,12 +45,12 @@ pub(super) fn write_library( backup_name.set_extension("bkp"); // Create a new BufWriter on the file and make a snap frame encoer for it too - let writer = BufWriter::new(fs::File::create(writer_name.to_path_buf())?); + let writer = BufWriter::new(fs::File::create(&writer_name)?); let mut e = snap::write::FrameEncoder::new(writer); // Write out the data using bincode bincode::serde::encode_into_std_write( - &library, + library, &mut e, bincode::config::standard() .with_little_endian() @@ -83,7 +83,7 @@ pub fn find_images(song_path: &PathBuf) -> Result, Box> continue; } - let format = FileFormat::from_file(&path)?.kind(); + let format = FileFormat::from_file(path)?.kind(); if format != Kind::Image { break } diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs index 37a3142..f5bee70 100644 --- a/src/music_tracker/music_tracker.rs +++ b/src/music_tracker/music_tracker.rs @@ -41,7 +41,7 @@ pub enum TrackerError { impl TrackerError { pub fn from_surf_error(error: surf::Error) -> TrackerError { - return match error.status() { + match error.status() { StatusCode::Forbidden => TrackerError::InvalidAuth, StatusCode::Unauthorized => TrackerError::InvalidAuth, StatusCode::NetworkAuthenticationRequired => TrackerError::InvalidAuth, @@ -50,7 +50,7 @@ impl TrackerError { StatusCode::ServiceUnavailable => TrackerError::ServiceUnavailable, StatusCode::NotFound => TrackerError::ServiceUnavailable, _ => TrackerError::Unknown, - }; + } } } @@ -85,8 +85,8 @@ impl MusicTracker for LastFM { }; params.insert("method", "track.scrobble"); - params.insert("artist", &artist); - params.insert("track", &track); + params.insert("artist", artist); + params.insert("track", track); params.insert("timestamp", &string_timestamp); return match self.api_request(params).await { @@ -104,8 +104,8 @@ impl MusicTracker for LastFM { }; params.insert("method", "track.updateNowPlaying"); - params.insert("artist", &artist); - params.insert("track", &track); + params.insert("artist", artist); + params.insert("track", track); return match self.api_request(params).await { Ok(_) => Ok(()), @@ -160,7 +160,7 @@ impl LastFM { auth_token.token ); - return Ok(auth_url); + Ok(auth_url) } /// Returns a LastFM session key @@ -186,15 +186,15 @@ impl LastFM { // Sets session key from received response let session_response: Session = serde_json::from_str(&response)?; - return Ok(session_response.session.key); + Ok(session_response.session.key) } /// Creates a new LastFM struct with a given config pub fn new(config: &LastFMConfig) -> LastFM { - let last_fm = LastFM { + + LastFM { config: config.clone(), - }; - return last_fm; + } } // Creates an api request with the given parameters @@ -221,9 +221,9 @@ impl LastFM { let url = "http://ws.audioscrobbler.com/2.0/"; - let response = surf::post(url).body_string(string_params).await; + - return response; + surf::post(url).body_string(string_params).await } // Returns an api signature as defined in the last.fm api documentation @@ -242,7 +242,7 @@ impl LastFM { let hash_result = md5_hasher.finalize(); let hashed_sig = format!("{:#02x}", hash_result); - return hashed_sig; + hashed_sig } // Removes last.fm account from dango-music-player @@ -265,11 +265,11 @@ pub struct DiscordRPC { impl DiscordRPC { pub fn new(config: &DiscordRPCConfig) -> Self { - let rpc = DiscordRPC { + + DiscordRPC { client: discord_presence::client::Client::new(config.dango_client_id), config: config.clone(), - }; - return rpc; + } } } @@ -307,8 +307,8 @@ impl MusicTracker for DiscordRPC { // Sets discord account activity to current playing song let send_activity = self.client.set_activity(|activity| { activity - .state(format!("{}", album)) - .details(format!("{}", song_name)) + .state(album.to_string()) + .details(song_name.to_string()) .assets(|assets| assets.large_image(&self.config.dango_icon)) .timestamps(|time| time.start(start_time)) }); @@ -425,10 +425,10 @@ impl ListenBrainz { request: &String, endpoint: &String, ) -> Result { - let reponse = surf::post(format!("{}{}", &self.config.api_url, endpoint)) + + surf::post(format!("{}{}", &self.config.api_url, endpoint)) .body_string(request.clone()) .header("Authorization", format!("Token {}", self.config.auth_token)) - .await; - return reponse; + .await } }