Final touches to adhere to the Rust API Guidelines

This commit is contained in:
Polochon-street 2021-05-17 21:09:24 +02:00
parent e29056aaa8
commit e2e2f2cad9
8 changed files with 200 additions and 378 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

306
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"
@ -106,16 +80,12 @@ dependencies = [
name = "bliss-rs" name = "bliss-rs"
version = "0.1.2" version = "0.1.2"
dependencies = [ dependencies = [
"anyhow",
"bliss-audio-aubio-rs", "bliss-audio-aubio-rs",
"clap",
"crossbeam", "crossbeam",
"dirs",
"env_logger", "env_logger",
"ffmpeg-next", "ffmpeg-next",
"lazy_static", "lazy_static",
"log", "log",
"mpd",
"ndarray", "ndarray",
"ndarray-npy", "ndarray-npy",
"ndarray-stats", "ndarray-stats",
@ -123,9 +93,8 @@ dependencies = [
"num_cpus", "num_cpus",
"rayon", "rayon",
"ripemd160", "ripemd160",
"rusqlite",
"rustfft", "rustfft",
"tempdir", "serde",
"thiserror", "thiserror",
] ]
@ -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

@ -7,6 +7,7 @@ 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

@ -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.;