Merge pull request #5 from Polochon-street/final-touches

Final touches to adhere to the Rust API Guidelines
This commit is contained in:
Polochon-street 2021-05-17 22:23:29 +02:00
commit 6a070a6d13
11 changed files with 312 additions and 412 deletions

3
CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
Change Log
All user visible changes to this project will be documented in this file. This project adheres to Semantic Versioning, as described for Rust libraries in RFC #1105

348
Cargo.lock generated
View file

@ -6,17 +6,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f200cbb1e856866d9eade941cf3aa0c5d7dd36f74311c4273b494f4ef036957"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
@ -26,21 +15,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
[[package]]
name = "atty"
version = "0.2.14"
@ -84,6 +58,28 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bliss-audio"
version = "0.1.3"
dependencies = [
"bliss-audio-aubio-rs",
"crossbeam",
"env_logger",
"ffmpeg-next",
"lazy_static",
"log",
"ndarray",
"ndarray-npy",
"ndarray-stats",
"noisy_float",
"num_cpus",
"rayon",
"ripemd160",
"rustfft",
"serde",
"thiserror",
]
[[package]]
name = "bliss-audio-aubio-rs"
version = "0.2.0"
@ -102,33 +98,6 @@ dependencies = [
"cc",
]
[[package]]
name = "bliss-rs"
version = "0.1.2"
dependencies = [
"anyhow",
"bliss-audio-aubio-rs",
"clap",
"crossbeam",
"dirs",
"env_logger",
"ffmpeg-next",
"lazy_static",
"log",
"mpd",
"ndarray",
"ndarray-npy",
"ndarray-stats",
"noisy_float",
"num_cpus",
"rayon",
"ripemd160",
"rusqlite",
"rustfft",
"tempdir",
"thiserror",
]
[[package]]
name = "block-buffer"
version = "0.7.3"
@ -159,12 +128,6 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "bufstream"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
[[package]]
name = "byte-tools"
version = "0.3.1"
@ -218,21 +181,6 @@ dependencies = [
"libloading",
]
[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "crc32fast"
version = "1.2.1"
@ -329,26 +277,6 @@ dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "dirs"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "either"
version = "1.6.1"
@ -374,18 +302,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "ffmpeg-next"
version = "4.3.8"
@ -423,12 +339,6 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "generic-array"
version = "0.12.4"
@ -471,24 +381,6 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "hashlink"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
dependencies = [
"hashbrown 0.11.2",
]
[[package]]
name = "hermit-abi"
version = "0.1.18"
@ -511,7 +403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
dependencies = [
"autocfg",
"hashbrown 0.9.1",
"hashbrown",
]
[[package]]
@ -560,16 +452,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "libsqlite3-sys"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "log"
version = "0.4.14"
@ -619,17 +501,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mpd"
version = "0.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a20784da57fa01bf7910a5da686d9f39ff37feaa774856b71f050e4331bf82"
dependencies = [
"bufstream",
"rustc-serialize",
"time",
]
[[package]]
name = "ndarray"
version = "0.15.1"
@ -669,7 +540,7 @@ dependencies = [
"noisy_float",
"num-integer",
"num-traits",
"rand 0.8.3",
"rand",
]
[[package]]
@ -749,12 +620,6 @@ dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "opaque-debug"
version = "0.2.3"
@ -868,19 +733,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand"
version = "0.8.3"
@ -889,7 +741,7 @@ checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [
"libc",
"rand_chacha",
"rand_core 0.6.2",
"rand_core",
"rand_hc",
]
@ -900,24 +752,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [
"ppv-lite86",
"rand_core 0.6.2",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.6.2"
@ -933,7 +770,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [
"rand_core 0.6.2",
"rand_core",
]
[[package]]
@ -967,34 +804,6 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.5.4"
@ -1012,15 +821,6 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "ripemd160"
version = "0.9.1"
@ -1032,33 +832,12 @@ dependencies = [
"opaque-debug 0.3.0",
]
[[package]]
name = "rusqlite"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57adcf67c8faaf96f3248c2a7b419a0dbc52ebe36ba83dd57fe83827c1ea4eb3"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"memchr",
"smallvec",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-serialize"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]]
name = "rustfft"
version = "5.1.1"
@ -1079,6 +858,26 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sha-1"
version = "0.8.2"
@ -1097,24 +896,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]]
name = "smallvec"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "strength_reduce"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3ff2f71c82567c565ba4b3009a9350a96a7269eaa4001ebedae926230bc2254"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.72"
@ -1126,16 +913,6 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"rand 0.4.6",
"remove_dir_all",
]
[[package]]
name = "termcolor"
version = "1.1.2"
@ -1145,15 +922,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.24"
@ -1174,16 +942,6 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "transpose"
version = "0.2.1"
@ -1206,12 +964,6 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
version = "0.2.2"
@ -1224,12 +976,6 @@ version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.3"

View file

@ -1,12 +1,13 @@
[package]
name = "bliss-rs"
version = "0.1.2"
name = "bliss-audio"
version = "0.1.3"
authors = ["Polochon-street <polochonstreet@gmx.fr>"]
edition = "2018"
license = "GPL-3.0-only"
description = "A song analysis library for making playlists"
homepage = "https://lelele.io/bliss.html"
repository = "https://github.com/Polochon-street/bliss-rs"
keywords = ["audio", "analysis", "MIR", "playlist"]
readme = "README.md"
[package.metadata.docs.rs]
@ -39,11 +40,4 @@ thiserror = "1.0.24"
# Until https://github.com/aubio/aubio/issues/336 is somehow solved
# Hopefully we'll be able to use the official aubio-rs at some point.
bliss-audio-aubio-rs = "0.2.0"
[dev-dependencies]
mpd = "0.0.12"
rusqlite = "0.25.0"
dirs = "3.0.1"
tempdir = "0.3.7"
clap = "2.33.3"
anyhow = "1.0.40"
serde = { version = "1.0", optional = true, features = ["derive"] }

View file

@ -1,18 +1,96 @@
![build](https://github.com/Polochon-street/bliss-rs/workflows/Rust/badge.svg)
![doc](https://docs.rs/bliss-rs/badge.svg)
[![crate](https://img.shields.io/crates/v/bliss-audio.svg)](https://crates.io/crates/bliss-audio)
[![build](https://github.com/Polochon-street/bliss-rs/workflows/Rust/badge.svg)](https://github.com/Polochon-street/bliss-rs/actions)
[![doc](https://docs.rs/bliss-rs/badge.svg)](https://docs.rs/bliss-audio/)
# Bliss music analyser - Rust version
Bliss-rs is the Rust improvement of [Bliss](https://github.com/Polochon-street/bliss). The data it
outputs is not compatible with the ones used by Bliss, since it uses
different, more accurate features, based on actual literature this time.
# bliss music analyser - Rust version
bliss-rs is the Rust improvement of [bliss](https://github.com/Polochon-street/bliss), a
library used to make playlists by analyzing songs, and computing distance between them.
Like Bliss, it ease the creation of « intelligent » playlists and/or continuous
play, à la Spotify/Grooveshark Radio.
Like bliss, it eases the creation of « intelligent » playlists and/or continuous
play, à la Spotify/Grooveshark Radio, as well as easing creating plug-ins for
existing audio players.
## Usage
For now (and if you're looking for an easy-to use smooth play experience),
[blissify](https://crates.io/crates/blissify) implements bliss for
[MPD](https://www.musicpd.org/).
Note 1: the features bliss-rs outputs is not compatible with the ones
used by C-bliss, since it uses
different, more accurate values, based on
[actual literature](https://lelele.io/thesis.pdf). It is also faster.
Note 2: The `bliss-rs` crate is outdated. You should use `bliss-audio`
(this crate) instead.
## Examples
For simple analysis / distance computing, a look at `examples/distance.rs` and
`examples/analyse.rs`.
Ready to use examples:
### Compute the distance between two songs
```
use bliss_audio::Song;
fn main() {
let song1 = Song::new("/path/to/song1");
let song2 = Song::new("/path/to/song2");
println!("Distance between song1 and song2 is {}", song1.distance(song2));
}
```
### Make a playlist from a song
```
use bliss_rs::{BlissError, Song};
use ndarray::{arr1, Array};
use noisy_float::prelude::n32;
fn main() -> Result<(), BlissError> {
let paths = vec!["/path/to/song1", "/path/to/song2", "/path/to/song3"];
let mut songs: Vec<Song> = paths
.iter()
.map(|path| Song::new(path))
.collect::<Result<Vec<Song>, BlissError>>()?;
// Assuming there is a first song
let analysis_first_song = arr1(&songs[0].analysis);
// Identity matrix used to compute the distance.
// Note that it can be changed to alter feature ponderation, which
// may yield to better playlists (subjectively).
let m = Array::eye(analysis_first_song.len());
songs.sort_by_cached_key(|song| {
n32((arr1(&song.analysis) - &analysis_first_song)
.dot(&m)
.dot(&(arr1(&song.analysis) - &analysis_first_song)))
});
println!(
"Playlist is: {:?}",
songs
.iter()
.map(|song| &song.path)
.collect::<Vec<&String>>()
);
Ok(())
}
```
## Further use
Instead of reinventing ways to fetch a user library, play songs, etc,
and embed that into bliss, it is easier to look at the
[Library](https://github.com/Polochon-street/bliss-rs/blob/master/src/library.rs#L12)
trait.
By implementing a few functions to get songs from a media library, and store
the resulting analysis, you get access to functions to analyze an entire
library (with multithreading), and to make playlists easily.
See [blissify](https://crates.io/crates/blissify) for a reference
implementation.
## Acknowledgements
* This library relies heavily on [aubio](https://aubio.org/)'s

View file

@ -1,4 +1,4 @@
use bliss_rs::Song;
use bliss_audio::Song;
use std::env;
/**

View file

@ -1,4 +1,4 @@
use bliss_rs::Song;
use bliss_audio::Song;
use std::env;
/**

View file

@ -258,11 +258,11 @@ fn pip_track(
let beginning = freq_mask
.iter()
.position(|&b| b)
.ok_or(BlissError::AnalysisError("in chroma".to_string()))?;
.ok_or_else(|| BlissError::AnalysisError("in chroma".to_string()))?;
let end = freq_mask
.iter()
.rposition(|&b| b)
.ok_or(BlissError::AnalysisError("in chroma".to_string()))?;
.ok_or_else(|| BlissError::AnalysisError("in chroma".to_string()))?;
let zipped = Zip::indexed(spectrum.slice(s![beginning..end - 3, ..]))
.and(spectrum.slice(s![beginning + 1..end - 2, ..]))

View file

@ -1,6 +1,27 @@
//! bliss is a library for making "smart" audio playlists.
//!
//! The core of the library is the `Song` object, which relates to a
//! specific analyzed song and contains its path, title, analysis, and
//! other metadata fields (album, genre...).
//! Analyzing a song is as simple as running `Song::new("/path/to/song")`.
//!
//! The [analysis](Song::analysis) field of each song is an array of f32, which makes the
//! comparison between songs easy, by just using euclidean distance (see
//! [distance](Song::distance) for instance).
//!
//! Once several songs have been analyzed, making a playlist from one Song
//! is as easy as computing distances between that song and the rest, and ordering
//! the songs by distance, ascending.
//!
//! It is also convenient to make plug-ins for existing audio players.
//! It should be as easy as implementing the necessary traits for [Library].
//! A reference implementation for the MPD player is available
//! [here](https://github.com/Polochon-street/blissify-rs)
#![cfg_attr(feature = "bench", feature(test))]
#![warn(missing_docs)]
#![warn(missing_doc_code_examples)]
mod chroma;
pub mod library;
mod library;
mod misc;
mod song;
mod temporal;
@ -9,37 +30,29 @@ mod utils;
extern crate crossbeam;
extern crate num_cpus;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
use thiserror::Error;
pub use song::Song;
pub use library::Library;
const CHANNELS: u16 = 1;
const SAMPLE_RATE: u32 = 22050;
#[derive(Default, Debug, PartialEq, Clone)]
/// Simple object used to represent a Song, with its path, analysis, and
/// other metadata (artist, genre...)
pub struct Song {
pub path: String,
pub artist: String,
pub title: String,
pub album: String,
pub track_number: String,
pub genre: String,
/// Vec containing analysis, in order: tempo, zero-crossing rate,
/// mean spectral centroid, std deviation spectral centroid,
/// mean spectral rolloff, std deviation spectral rolloff
/// mean spectral_flatness, std deviation spectral flatness,
/// mean loudness, std deviation loudness
/// chroma interval feature 1 to 10
pub analysis: Vec<f32>,
}
#[derive(Error, Debug, PartialEq)]
/// Umbrella type for bliss error types
pub enum BlissError {
#[error("Error happened while decoding file {0}")]
#[error("error happened while decoding file {0}")]
/// An error happened while decoding an (audio) file
DecodingError(String),
#[error("Error happened while analyzing file {0}")]
#[error("error happened while analyzing file {0}")]
/// An error happened during the analysis of the samples by bliss
AnalysisError(String),
#[error("Error happened with the music library provider - {0}")]
#[error("error happened with the music library provider - {0}")]
/// An error happened with the music library provider.
/// Useful to report errors when you implement the [Library] trait.
ProviderError(String),
}
@ -83,6 +96,18 @@ pub fn bulk_analyse(paths: Vec<String>) -> Vec<Result<Song, BlissError>> {
mod tests {
use super::*;
#[test]
fn test_send_song() {
fn assert_send<T: Send>() {}
assert_send::<Song>();
}
#[test]
fn test_sync_song() {
fn assert_sync<T: Send>() {}
assert_sync::<Song>();
}
#[test]
fn test_bulk_analyse() {
let results = bulk_analyse(vec![
@ -113,7 +138,7 @@ mod tests {
assert_eq!(
vec![
String::from(
"Error happened while decoding file while opening format: ffmpeg::Error(2: No such file or directory)."
"error happened while decoding file while opening format: ffmpeg::Error(2: No such file or directory)."
);
8
],

View file

@ -68,6 +68,7 @@ pub trait Library {
}
let num_cpus = num_cpus::get();
#[allow(clippy::type_complexity)]
let (tx, rx): (
Sender<(String, Result<Song, BlissError>)>,
Receiver<(String, Result<Song, BlissError>)>,
@ -98,8 +99,8 @@ pub trait Library {
match song {
Ok(song) => {
self.store_song(&song)
.unwrap_or_else(|_| error!("Error while storing song '{}'", (&song).path));
info!("Analyzed and stored song '{}' successfully.", (&song).path)
.unwrap_or_else(|_| error!("Error while storing song '{}'", song.path));
info!("Analyzed and stored song '{}' successfully.", song.path)
}
Err(e) => {
self.store_error_song(path.to_string(), e)
@ -114,7 +115,7 @@ pub trait Library {
for child in handles {
child
.join()
.map_err(|_| BlissError::AnalysisError(format!("in analysis")))?;
.map_err(|_| BlissError::AnalysisError("in analysis".to_string()))?;
}
Ok(())
}
@ -236,7 +237,7 @@ mod test {
assert_eq!(
test_library.analyze_library(),
Err(BlissError::ProviderError(String::from(
"Error happened with the music library provider - Could not get songs path"
"error happened with the music library provider - Could not get songs path"
))),
);
}

View file

@ -3,6 +3,9 @@
//! Use decoding, and features-extraction functions from other modules
//! e.g. tempo features, spectral features, etc to build a Song and its
//! corresponding Analysis.
//!
//! For implementation of plug-ins for already existing audio players,
//! a look at Library is instead recommended.
extern crate crossbeam;
extern crate ffmpeg_next as ffmpeg;
@ -14,7 +17,7 @@ use crate::chroma::ChromaDesc;
use crate::misc::LoudnessDesc;
use crate::temporal::BPMDesc;
use crate::timbral::{SpectralDesc, ZeroCrossingRateDesc};
use crate::{BlissError, Song, SAMPLE_RATE};
use crate::{BlissError, SAMPLE_RATE};
use ::log::warn;
use crossbeam::thread;
use ffmpeg_next::codec::threading::{Config, Type as ThreadingType};
@ -32,73 +35,41 @@ use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::thread as std_thread;
fn push_to_sample_array(frame: &ffmpeg::frame::Audio, sample_array: &mut Vec<f32>) {
if frame.samples() == 0 {
return;
}
// Account for the padding
let actual_size = util::format::sample::Buffer::size(
Sample::F32(Type::Packed),
CHANNELS,
frame.samples(),
false,
);
let f32_frame: Vec<f32> = frame.data(0)[..actual_size]
.chunks_exact(4)
.map(|x| {
let mut a: [u8; 4] = [0; 4];
a.copy_from_slice(x);
f32::from_le_bytes(a)
})
.collect();
sample_array.extend_from_slice(&f32_frame);
}
#[derive(Default, Debug)]
pub(crate) struct InternalSong {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Default, Debug, PartialEq, Clone)]
/// Simple object used to represent a Song, with its path, analysis, and
/// other metadata (artist, genre...)
pub struct Song {
/// Song's provided file path
pub path: String,
/// Song's artist, read from the metadata (`""` if empty)
pub artist: String,
/// Song's title, read from the metadata (`""` if empty)
pub title: String,
/// Song's album name, read from the metadata (`""` if empty)
pub album: String,
/// Song's tracked number, read from the metadata (`""` if empty)
pub track_number: String,
/// Song's genre, read from the metadata (`""` if empty)
pub genre: String,
pub sample_array: Vec<f32>,
}
fn resample_frame(
rx: Receiver<Audio>,
mut resample_context: Context,
mut sample_array: Vec<f32>,
) -> Result<Vec<f32>, BlissError> {
let mut resampled = ffmpeg::frame::Audio::empty();
for decoded in rx.iter() {
resample_context
.run(&decoded, &mut resampled)
.map_err(|e| {
BlissError::DecodingError(format!("while trying to resample song: {:?}", e))
})?;
push_to_sample_array(&resampled, &mut sample_array);
}
loop {
match resample_context.flush(&mut resampled).map_err(|e| {
BlissError::DecodingError(format!("while trying to resample song: {:?}", e))
})? {
Some(_) => {
push_to_sample_array(&resampled, &mut sample_array);
}
None => {
if resampled.samples() == 0 {
break;
}
push_to_sample_array(&resampled, &mut sample_array);
}
};
}
Ok(sample_array)
/// Vec containing analysis, in order: tempo, zero-crossing rate,
/// mean spectral centroid, std deviation spectral centroid,
/// mean spectral rolloff, std deviation spectral rolloff
/// mean spectral_flatness, std deviation spectral flatness,
/// mean loudness, std deviation loudness, chroma interval feature 1 to 10.
///
/// All the numbers are between -1 and 1.
pub analysis: Vec<f32>,
}
impl Song {
#[allow(dead_code)]
/// Compute the distance between the current song and any given Song.
///
/// The smaller the number, the closer the songs; usually more useful
/// if compared between several songs
/// (e.g. if song1.distance(song2) < song1.distance(song3), then song1 is
/// closer to song2 than it is to song3.
pub fn distance(&self, other: &Self) -> f32 {
let a1 = arr1(&self.analysis.to_vec());
let a2 = arr1(&other.analysis.to_vec());
@ -110,6 +81,22 @@ impl Song {
(arr1(&self.analysis) - &a2).dot(&m).dot(&(&a1 - &a2))
}
/// Returns a decoded Song given a file path, or an error if the song
/// could not be analyzed for some reason.
///
/// # Arguments
///
/// * `path` - A string holding a valid file path to a valid audio file.
///
/// # Errors
///
/// This function will return an error if the file path is invalid, if
/// the file path points to a file containing no or corrupted audio stream,
/// or if the analysis could not be conducted to the end for some reason.
///
/// The error type returned should give a hint as to whether it was a
/// decoding ([DecodingError](BlissError::DecodingError)) or an analysis
/// ([AnalysisError](BlissError::AnalysisError)) error.
pub fn new(path: &str) -> Result<Self, BlissError> {
let raw_song = Song::decode(&path)?;
@ -172,6 +159,7 @@ impl Song {
Ok(chroma_desc.get_values())
});
#[allow(clippy::type_complexity)]
let child_timbral: thread::ScopedJoinHandle<
'_,
Result<(Vec<f32>, Vec<f32>, Vec<f32>), BlissError>,
@ -238,7 +226,7 @@ impl Song {
let stream = format
.streams()
.find(|s| s.codec().medium() == ffmpeg::media::Type::Audio)
.ok_or(BlissError::DecodingError(String::from(
.ok_or_else(|| BlissError::DecodingError(String::from(
"No audio stream found.",
)))?;
stream.codec().set_threading(Config {
@ -379,6 +367,71 @@ impl Song {
}
}
#[derive(Default, Debug)]
pub(crate) struct InternalSong {
pub path: String,
pub artist: String,
pub title: String,
pub album: String,
pub track_number: String,
pub genre: String,
pub sample_array: Vec<f32>,
}
fn resample_frame(
rx: Receiver<Audio>,
mut resample_context: Context,
mut sample_array: Vec<f32>,
) -> Result<Vec<f32>, BlissError> {
let mut resampled = ffmpeg::frame::Audio::empty();
for decoded in rx.iter() {
resample_context
.run(&decoded, &mut resampled)
.map_err(|e| {
BlissError::DecodingError(format!("while trying to resample song: {:?}", e))
})?;
push_to_sample_array(&resampled, &mut sample_array);
}
loop {
match resample_context.flush(&mut resampled).map_err(|e| {
BlissError::DecodingError(format!("while trying to resample song: {:?}", e))
})? {
Some(_) => {
push_to_sample_array(&resampled, &mut sample_array);
}
None => {
if resampled.samples() == 0 {
break;
}
push_to_sample_array(&resampled, &mut sample_array);
}
};
}
Ok(sample_array)
}
fn push_to_sample_array(frame: &ffmpeg::frame::Audio, sample_array: &mut Vec<f32>) {
if frame.samples() == 0 {
return;
}
// Account for the padding
let actual_size = util::format::sample::Buffer::size(
Sample::F32(Type::Packed),
CHANNELS,
frame.samples(),
false,
);
let f32_frame: Vec<f32> = frame.data(0)[..actual_size]
.chunks_exact(4)
.map(|x| {
let mut a: [u8; 4] = [0; 4];
a.copy_from_slice(x);
f32::from_le_bytes(a)
})
.collect();
sample_array.extend_from_slice(&f32_frame);
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -103,7 +103,7 @@ pub(crate) fn geometric_mean(input: &[f32]) -> f32 {
for ch in input.chunks_exact(8) {
let mut m;
m = (ch[0] as f64 * ch[1] as f64) * (ch[2] as f64 * ch[3] as f64);
m *= 3.27339060789614187e150; // 2^500 : avoid underflows and denormals
m *= 3.273390607896142e150; // 2^500 : avoid underflows and denormals
m *= (ch[4] as f64 * ch[5] as f64) * (ch[6] as f64 * ch[7] as f64);
if m == 0. {
return 0.;