Add an analyze_paths_streaming
function
This commit is contained in:
parent
bd6ff422a7
commit
0975fa1fd4
6 changed files with 116 additions and 6 deletions
|
@ -1,5 +1,8 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## bliss 0.3.3
|
||||||
|
* Add a streaming analysis function, to help libraries displaying progress.
|
||||||
|
|
||||||
## bliss 0.3.2
|
## bliss 0.3.2
|
||||||
* Fixed a rare ffmpeg multithreading bug.
|
* Fixed a rare ffmpeg multithreading bug.
|
||||||
|
|
||||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -75,7 +75,7 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bliss-audio"
|
name = "bliss-audio"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bliss-audio-aubio-rs",
|
"bliss-audio-aubio-rs",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bliss-audio"
|
name = "bliss-audio"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
authors = ["Polochon-street <polochonstreet@gmx.fr>"]
|
authors = ["Polochon-street <polochonstreet@gmx.fr>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
#![warn(missing_doc_code_examples)]
|
#![warn(missing_doc_code_examples)]
|
||||||
mod chroma;
|
mod chroma;
|
||||||
pub mod distance;
|
pub mod distance;
|
||||||
mod library;
|
pub mod library;
|
||||||
mod misc;
|
mod misc;
|
||||||
mod song;
|
mod song;
|
||||||
mod temporal;
|
mod temporal;
|
||||||
|
|
109
src/library.rs
109
src/library.rs
|
@ -1,5 +1,8 @@
|
||||||
//! Module containing the Library trait, useful to get started to implement
|
//! Module containing the Library trait, useful to get started to implement
|
||||||
//! a plug-in for an audio player.
|
//! a plug-in for an audio player.
|
||||||
|
//!
|
||||||
|
//! Looking at the [reference implementation for
|
||||||
|
//! MPD](https://github.com/Polochon-street/blissify-rs) could also be useful.
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use crate::distance;
|
use crate::distance;
|
||||||
use crate::distance::DistanceMetric;
|
use crate::distance::DistanceMetric;
|
||||||
|
@ -116,7 +119,7 @@ pub trait Library {
|
||||||
/// Analyze and store songs in `paths`, using `store_song` and
|
/// Analyze and store songs in `paths`, using `store_song` and
|
||||||
/// `store_error_song` implementations.
|
/// `store_error_song` implementations.
|
||||||
///
|
///
|
||||||
/// Note: this is mostly useful for updating a song library. For the first
|
/// note: this is mostly useful for updating a song library. for the first
|
||||||
/// run, you probably want to use `analyze_library`.
|
/// run, you probably want to use `analyze_library`.
|
||||||
fn analyze_paths(&mut self, paths: Vec<String>) -> BlissResult<()> {
|
fn analyze_paths(&mut self, paths: Vec<String>) -> BlissResult<()> {
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
|
@ -192,6 +195,82 @@ pub trait Library {
|
||||||
self.analyze_paths(paths)?;
|
self.analyze_paths(paths)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Analyze an entire library using `get_songs_paths`, but instead of
|
||||||
|
/// storing songs using [store_song](Library::store_song)
|
||||||
|
/// and [store_error_song](Library::store_error_song).
|
||||||
|
///
|
||||||
|
/// Returns an iterable [Receiver], whose items are a tuple made of
|
||||||
|
/// the song path (to display to the user in case the analysis failed),
|
||||||
|
/// and a Result<Song>.
|
||||||
|
fn analyze_library_streaming(&mut self) -> BlissResult<Receiver<(String, BlissResult<Song>)>> {
|
||||||
|
let paths = self
|
||||||
|
.get_songs_paths()
|
||||||
|
.map_err(|e| BlissError::ProviderError(e.to_string()))?;
|
||||||
|
analyze_paths_streaming(paths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Analyze songs in `paths`, and return the analyzed [Song] objects through a
|
||||||
|
/// [Receiver].
|
||||||
|
///
|
||||||
|
/// Returns an iterable [Receiver], whose items are a tuple made of
|
||||||
|
/// the song path (to display to the user in case the analysis failed),
|
||||||
|
/// and a Result<Song>.
|
||||||
|
///
|
||||||
|
/// Note: this is mostly useful for updating a song library, while displaying
|
||||||
|
/// status to the user (since you have access to each song object). For the
|
||||||
|
/// first run, you probably want to use `analyze_library`.
|
||||||
|
///
|
||||||
|
/// * Example:
|
||||||
|
/// ```no_run
|
||||||
|
/// use bliss_audio::{library::analyze_paths_streaming, BlissResult};
|
||||||
|
///
|
||||||
|
/// fn main() -> BlissResult<()> {
|
||||||
|
/// let paths = vec![String::from("/path/to/song1"), String::from("/path/to/song2")];
|
||||||
|
/// let rx = analyze_paths_streaming(paths)?;
|
||||||
|
/// for (path, result) in rx.iter() {
|
||||||
|
/// match result {
|
||||||
|
/// Ok(song) => println!("Do something with analyzed song {} with title {:?}", song.path.display(), song.title),
|
||||||
|
/// Err(e) => println!("Song at {} could not be analyzed. Failed with: {}", path, e),
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn analyze_paths_streaming(
|
||||||
|
paths: Vec<String>,
|
||||||
|
) -> BlissResult<Receiver<(String, BlissResult<Song>)>> {
|
||||||
|
let num_cpus = num_cpus::get();
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
let (tx, rx): (
|
||||||
|
Sender<(String, BlissResult<Song>)>,
|
||||||
|
Receiver<(String, BlissResult<Song>)>,
|
||||||
|
) = mpsc::channel();
|
||||||
|
if paths.is_empty() {
|
||||||
|
return Ok(rx);
|
||||||
|
}
|
||||||
|
let mut handles = Vec::new();
|
||||||
|
let mut chunk_length = paths.len() / num_cpus;
|
||||||
|
if chunk_length == 0 {
|
||||||
|
chunk_length = paths.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
for chunk in paths.chunks(chunk_length) {
|
||||||
|
let tx_thread = tx.clone();
|
||||||
|
let owned_chunk = chunk.to_owned();
|
||||||
|
let child = thread::spawn(move || {
|
||||||
|
for path in owned_chunk {
|
||||||
|
info!("Analyzing file '{}'", path);
|
||||||
|
let song = Song::new(&path);
|
||||||
|
tx_thread.send((path.to_string(), song)).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
handles.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -325,6 +404,34 @@ mod test {
|
||||||
assert!(test_library.analyze_library().is_ok())
|
assert!(test_library.analyze_library().is_ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_analyze_library_streaming() {
|
||||||
|
let mut test_library = TestLibrary {
|
||||||
|
internal_storage: vec![],
|
||||||
|
failed_files: vec![],
|
||||||
|
};
|
||||||
|
let rx = test_library.analyze_library_streaming().unwrap();
|
||||||
|
|
||||||
|
let mut result = rx.iter().collect::<Vec<(String, BlissResult<Song>)>>();
|
||||||
|
result.sort_by_key(|k| k.0.to_owned());
|
||||||
|
let expected = result
|
||||||
|
.iter()
|
||||||
|
.map(|x| match &x.1 {
|
||||||
|
Ok(s) => (true, s.path.to_string_lossy().to_string()),
|
||||||
|
Err(_) => (false, x.0.to_owned()),
|
||||||
|
})
|
||||||
|
.collect::<Vec<(bool, String)>>();
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
(true, String::from("./data/s16_mono_22_5kHz.flac")),
|
||||||
|
(true, String::from("./data/white_noise.flac")),
|
||||||
|
(false, String::from("definitely-not-existing.foo")),
|
||||||
|
(false, String::from("not-existing.foo")),
|
||||||
|
],
|
||||||
|
expected,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_analyze_library() {
|
fn test_analyze_library() {
|
||||||
let mut test_library = TestLibrary {
|
let mut test_library = TestLibrary {
|
||||||
|
|
|
@ -466,7 +466,7 @@ impl Song {
|
||||||
song.sample_array = child.join().unwrap()?;
|
song.sample_array = child.join().unwrap()?;
|
||||||
return Ok(song);
|
return Ok(song);
|
||||||
}
|
}
|
||||||
Err(e) => warn!("decoding error: {}", e),
|
Err(e) => warn!("error while decoding {}: {}", path.display(), e),
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -500,7 +500,7 @@ impl Song {
|
||||||
song.sample_array = child.join().unwrap()?;
|
song.sample_array = child.join().unwrap()?;
|
||||||
return Ok(song);
|
return Ok(song);
|
||||||
}
|
}
|
||||||
Err(e) => warn!("decoding error: {}", e),
|
Err(e) => warn!("error while decoding {}: {}", path.display(), e),
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
Loading…
Reference in a new issue