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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 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]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.18"
@ -26,21 +15,6 @@ dependencies = [
"memchr", "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]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -84,6 +58,28 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 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]] [[package]]
name = "bliss-audio-aubio-rs" name = "bliss-audio-aubio-rs"
version = "0.2.0" version = "0.2.0"
@ -102,33 +98,6 @@ dependencies = [
"cc", "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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.7.3" version = "0.7.3"
@ -159,12 +128,6 @@ dependencies = [
"byte-tools", "byte-tools",
] ]
[[package]]
name = "bufstream"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
[[package]] [[package]]
name = "byte-tools" name = "byte-tools"
version = "0.3.1" version = "0.3.1"
@ -218,21 +181,6 @@ dependencies = [
"libloading", "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]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.2.1" version = "1.2.1"
@ -329,26 +277,6 @@ dependencies = [
"generic-array 0.14.4", "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]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@ -374,18 +302,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 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]] [[package]]
name = "ffmpeg-next" name = "ffmpeg-next"
version = "4.3.8" version = "4.3.8"
@ -423,12 +339,6 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.12.4" version = "0.12.4"
@ -471,24 +381,6 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 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]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.18" version = "0.1.18"
@ -511,7 +403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"hashbrown 0.9.1", "hashbrown",
] ]
[[package]] [[package]]
@ -560,16 +452,6 @@ dependencies = [
"winapi", "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]] [[package]]
name = "log" name = "log"
version = "0.4.14" version = "0.4.14"
@ -619,17 +501,6 @@ dependencies = [
"autocfg", "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]] [[package]]
name = "ndarray" name = "ndarray"
version = "0.15.1" version = "0.15.1"
@ -669,7 +540,7 @@ dependencies = [
"noisy_float", "noisy_float",
"num-integer", "num-integer",
"num-traits", "num-traits",
"rand 0.8.3", "rand",
] ]
[[package]] [[package]]
@ -749,12 +620,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "once_cell"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
version = "0.2.3" version = "0.2.3"
@ -868,19 +733,6 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rand" name = "rand"
version = "0.8.3" version = "0.8.3"
@ -889,7 +741,7 @@ checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha",
"rand_core 0.6.2", "rand_core",
"rand_hc", "rand_hc",
] ]
@ -900,24 +752,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [ dependencies = [
"ppv-lite86", "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]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.2" version = "0.6.2"
@ -933,7 +770,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [ dependencies = [
"rand_core 0.6.2", "rand_core",
] ]
[[package]] [[package]]
@ -967,34 +804,6 @@ dependencies = [
"num_cpus", "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]] [[package]]
name = "regex" name = "regex"
version = "1.5.4" version = "1.5.4"
@ -1012,15 +821,6 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 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]] [[package]]
name = "ripemd160" name = "ripemd160"
version = "0.9.1" version = "0.9.1"
@ -1032,33 +832,12 @@ dependencies = [
"opaque-debug 0.3.0", "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]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "1.1.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-serialize"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]] [[package]]
name = "rustfft" name = "rustfft"
version = "5.1.1" version = "5.1.1"
@ -1079,6 +858,26 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 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]] [[package]]
name = "sha-1" name = "sha-1"
version = "0.8.2" version = "0.8.2"
@ -1097,24 +896,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]]
name = "smallvec"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]] [[package]]
name = "strength_reduce" name = "strength_reduce"
version = "0.2.3" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3ff2f71c82567c565ba4b3009a9350a96a7269eaa4001ebedae926230bc2254" checksum = "a3ff2f71c82567c565ba4b3009a9350a96a7269eaa4001ebedae926230bc2254"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.72" version = "1.0.72"
@ -1126,16 +913,6 @@ dependencies = [
"unicode-xid", "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]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.1.2" version = "1.1.2"
@ -1145,15 +922,6 @@ dependencies = [
"winapi-util", "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]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.24" version = "1.0.24"
@ -1174,16 +942,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "transpose" name = "transpose"
version = "0.2.1" version = "0.2.1"
@ -1206,12 +964,6 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"
@ -1224,12 +976,6 @@ version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.3" version = "0.9.3"

View file

@ -1,12 +1,13 @@
[package] [package]
name = "bliss-rs" name = "bliss-audio"
version = "0.1.2" version = "0.1.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"
description = "A song analysis library for making playlists" description = "A song analysis library for making playlists"
homepage = "https://lelele.io/bliss.html" homepage = "https://lelele.io/bliss.html"
repository = "https://github.com/Polochon-street/bliss-rs" repository = "https://github.com/Polochon-street/bliss-rs"
keywords = ["audio", "analysis", "MIR", "playlist"]
readme = "README.md" readme = "README.md"
[package.metadata.docs.rs] [package.metadata.docs.rs]
@ -39,11 +40,4 @@ thiserror = "1.0.24"
# Until https://github.com/aubio/aubio/issues/336 is somehow solved # 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. # Hopefully we'll be able to use the official aubio-rs at some point.
bliss-audio-aubio-rs = "0.2.0" bliss-audio-aubio-rs = "0.2.0"
serde = { version = "1.0", optional = true, features = ["derive"] }
[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"

View file

@ -1,18 +1,96 @@
![build](https://github.com/Polochon-street/bliss-rs/workflows/Rust/badge.svg) [![crate](https://img.shields.io/crates/v/bliss-audio.svg)](https://crates.io/crates/bliss-audio)
![doc](https://docs.rs/bliss-rs/badge.svg) [![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 music analyser - Rust version
Bliss-rs is the Rust improvement of [Bliss](https://github.com/Polochon-street/bliss). The data it bliss-rs is the Rust improvement of [bliss](https://github.com/Polochon-street/bliss), a
outputs is not compatible with the ones used by Bliss, since it uses library used to make playlists by analyzing songs, and computing distance between them.
different, more accurate features, based on actual literature this time.
Like Bliss, it ease the creation of « intelligent » playlists and/or continuous Like bliss, it eases the creation of « intelligent » playlists and/or continuous
play, à la Spotify/Grooveshark Radio. 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 For simple analysis / distance computing, a look at `examples/distance.rs` and
`examples/analyse.rs`. `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 ## Acknowledgements
* This library relies heavily on [aubio](https://aubio.org/)'s * 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; use std::env;
/** /**

View file

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

View file

@ -258,11 +258,11 @@ fn pip_track(
let beginning = freq_mask let beginning = freq_mask
.iter() .iter()
.position(|&b| b) .position(|&b| b)
.ok_or(BlissError::AnalysisError("in chroma".to_string()))?; .ok_or_else(|| BlissError::AnalysisError("in chroma".to_string()))?;
let end = freq_mask let end = freq_mask
.iter() .iter()
.rposition(|&b| b) .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, ..])) let zipped = Zip::indexed(spectrum.slice(s![beginning..end - 3, ..]))
.and(spectrum.slice(s![beginning + 1..end - 2, ..])) .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))] #![cfg_attr(feature = "bench", feature(test))]
#![warn(missing_docs)]
#![warn(missing_doc_code_examples)]
mod chroma; mod chroma;
pub mod library; mod library;
mod misc; mod misc;
mod song; mod song;
mod temporal; mod temporal;
@ -9,37 +30,29 @@ mod utils;
extern crate crossbeam; extern crate crossbeam;
extern crate num_cpus; extern crate num_cpus;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
use thiserror::Error; use thiserror::Error;
pub use song::Song;
pub use library::Library;
const CHANNELS: u16 = 1; const CHANNELS: u16 = 1;
const SAMPLE_RATE: u32 = 22050; 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)] #[derive(Error, Debug, PartialEq)]
/// Umbrella type for bliss error types
pub enum BlissError { 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), 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), 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), ProviderError(String),
} }
@ -83,6 +96,18 @@ pub fn bulk_analyse(paths: Vec<String>) -> Vec<Result<Song, BlissError>> {
mod tests { mod tests {
use super::*; 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] #[test]
fn test_bulk_analyse() { fn test_bulk_analyse() {
let results = bulk_analyse(vec![ let results = bulk_analyse(vec![
@ -113,7 +138,7 @@ mod tests {
assert_eq!( assert_eq!(
vec![ vec![
String::from( 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 8
], ],

View file

@ -68,6 +68,7 @@ pub trait Library {
} }
let num_cpus = num_cpus::get(); let num_cpus = num_cpus::get();
#[allow(clippy::type_complexity)]
let (tx, rx): ( let (tx, rx): (
Sender<(String, Result<Song, BlissError>)>, Sender<(String, Result<Song, BlissError>)>,
Receiver<(String, Result<Song, BlissError>)>, Receiver<(String, Result<Song, BlissError>)>,
@ -98,8 +99,8 @@ pub trait Library {
match song { match song {
Ok(song) => { Ok(song) => {
self.store_song(&song) self.store_song(&song)
.unwrap_or_else(|_| error!("Error while storing song '{}'", (&song).path)); .unwrap_or_else(|_| error!("Error while storing song '{}'", song.path));
info!("Analyzed and stored song '{}' successfully.", (&song).path) info!("Analyzed and stored song '{}' successfully.", song.path)
} }
Err(e) => { Err(e) => {
self.store_error_song(path.to_string(), e) self.store_error_song(path.to_string(), e)
@ -114,7 +115,7 @@ pub trait Library {
for child in handles { for child in handles {
child child
.join() .join()
.map_err(|_| BlissError::AnalysisError(format!("in analysis")))?; .map_err(|_| BlissError::AnalysisError("in analysis".to_string()))?;
} }
Ok(()) Ok(())
} }
@ -236,7 +237,7 @@ mod test {
assert_eq!( assert_eq!(
test_library.analyze_library(), test_library.analyze_library(),
Err(BlissError::ProviderError(String::from( 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 //! Use decoding, and features-extraction functions from other modules
//! e.g. tempo features, spectral features, etc to build a Song and its //! e.g. tempo features, spectral features, etc to build a Song and its
//! corresponding Analysis. //! corresponding Analysis.
//!
//! For implementation of plug-ins for already existing audio players,
//! a look at Library is instead recommended.
extern crate crossbeam; extern crate crossbeam;
extern crate ffmpeg_next as ffmpeg; extern crate ffmpeg_next as ffmpeg;
@ -14,7 +17,7 @@ use crate::chroma::ChromaDesc;
use crate::misc::LoudnessDesc; use crate::misc::LoudnessDesc;
use crate::temporal::BPMDesc; use crate::temporal::BPMDesc;
use crate::timbral::{SpectralDesc, ZeroCrossingRateDesc}; use crate::timbral::{SpectralDesc, ZeroCrossingRateDesc};
use crate::{BlissError, Song, SAMPLE_RATE}; use crate::{BlissError, SAMPLE_RATE};
use ::log::warn; use ::log::warn;
use crossbeam::thread; use crossbeam::thread;
use ffmpeg_next::codec::threading::{Config, Type as ThreadingType}; use ffmpeg_next::codec::threading::{Config, Type as ThreadingType};
@ -32,73 +35,41 @@ use std::sync::mpsc;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::thread as std_thread; use std::thread as std_thread;
fn push_to_sample_array(frame: &ffmpeg::frame::Audio, sample_array: &mut Vec<f32>) { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
if frame.samples() == 0 { #[derive(Default, Debug, PartialEq, Clone)]
return; /// Simple object used to represent a Song, with its path, analysis, and
} /// other metadata (artist, genre...)
// Account for the padding pub struct Song {
let actual_size = util::format::sample::Buffer::size( /// Song's provided file path
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 {
pub path: String, pub path: String,
/// Song's artist, read from the metadata (`""` if empty)
pub artist: String, pub artist: String,
/// Song's title, read from the metadata (`""` if empty)
pub title: String, pub title: String,
/// Song's album name, read from the metadata (`""` if empty)
pub album: String, pub album: String,
/// Song's tracked number, read from the metadata (`""` if empty)
pub track_number: String, pub track_number: String,
/// Song's genre, read from the metadata (`""` if empty)
pub genre: String, pub genre: String,
pub sample_array: Vec<f32>, /// Vec containing analysis, in order: tempo, zero-crossing rate,
} /// mean spectral centroid, std deviation spectral centroid,
/// mean spectral rolloff, std deviation spectral rolloff
fn resample_frame( /// mean spectral_flatness, std deviation spectral flatness,
rx: Receiver<Audio>, /// mean loudness, std deviation loudness, chroma interval feature 1 to 10.
mut resample_context: Context, ///
mut sample_array: Vec<f32>, /// All the numbers are between -1 and 1.
) -> Result<Vec<f32>, BlissError> { pub analysis: Vec<f32>,
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)
} }
impl Song { impl Song {
#[allow(dead_code)] #[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 { pub fn distance(&self, other: &Self) -> f32 {
let a1 = arr1(&self.analysis.to_vec()); let a1 = arr1(&self.analysis.to_vec());
let a2 = arr1(&other.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)) (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> { pub fn new(path: &str) -> Result<Self, BlissError> {
let raw_song = Song::decode(&path)?; let raw_song = Song::decode(&path)?;
@ -172,6 +159,7 @@ impl Song {
Ok(chroma_desc.get_values()) Ok(chroma_desc.get_values())
}); });
#[allow(clippy::type_complexity)]
let child_timbral: thread::ScopedJoinHandle< let child_timbral: thread::ScopedJoinHandle<
'_, '_,
Result<(Vec<f32>, Vec<f32>, Vec<f32>), BlissError>, Result<(Vec<f32>, Vec<f32>, Vec<f32>), BlissError>,
@ -238,7 +226,7 @@ impl Song {
let stream = format let stream = format
.streams() .streams()
.find(|s| s.codec().medium() == ffmpeg::media::Type::Audio) .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.", "No audio stream found.",
)))?; )))?;
stream.codec().set_threading(Config { 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -103,7 +103,7 @@ pub(crate) fn geometric_mean(input: &[f32]) -> f32 {
for ch in input.chunks_exact(8) { for ch in input.chunks_exact(8) {
let mut m; let mut m;
m = (ch[0] as f64 * ch[1] as f64) * (ch[2] as f64 * ch[3] as f64); 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); m *= (ch[4] as f64 * ch[5] as f64) * (ch[6] as f64 * ch[7] as f64);
if m == 0. { if m == 0. {
return 0.; return 0.;