Compare commits
1 commit
symphonia
...
change-ana
Author | SHA1 | Date | |
---|---|---|---|
|
b0a96e257e |
7 changed files with 514 additions and 107 deletions
|
@ -1,3 +1,7 @@
|
|||
Change Log
|
||||
# Changelog
|
||||
|
||||
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
|
||||
## bliss 0.2.1
|
||||
|
||||
* Added an `Analysis` struct to `Song`, as well as an `AnalysisIndex` to
|
||||
index it easily.
|
||||
* Changed some logging parameters for the Library trait.
|
||||
|
|
270
Cargo.lock
generated
270
Cargo.lock
generated
|
@ -6,15 +6,30 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
|
||||
dependencies = [
|
||||
"memchr 0.1.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"memchr 2.4.0",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -23,7 +38,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
|||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -42,12 +57,12 @@ dependencies = [
|
|||
"cexpr",
|
||||
"cfg-if 0.1.10",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazy_static 1.4.0",
|
||||
"lazycell",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"regex 1.5.4",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
]
|
||||
|
@ -60,13 +75,13 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
|||
|
||||
[[package]]
|
||||
name = "bliss-audio"
|
||||
version = "0.1.3"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"bliss-audio-aubio-rs",
|
||||
"crossbeam",
|
||||
"env_logger",
|
||||
"ffmpeg-next",
|
||||
"lazy_static",
|
||||
"lazy_static 1.4.0",
|
||||
"log",
|
||||
"ndarray",
|
||||
"ndarray-npy",
|
||||
|
@ -77,6 +92,8 @@ dependencies = [
|
|||
"ripemd160",
|
||||
"rustfft",
|
||||
"serde",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
|
@ -96,6 +113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9ef9fab7b922bdd057bb06fa2a2fa79d2a93bec3dd576320511cb3dfe21e78a9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"fftw-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -140,6 +158,27 @@ version = "1.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.10+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17fa3d1ac1ca21c5c4e36a97f3c3eb25084576f6fc47bf0139c1123434216c6c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.67"
|
||||
|
@ -170,6 +209,16 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00"
|
||||
dependencies = [
|
||||
"num",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "0.29.3"
|
||||
|
@ -233,7 +282,7 @@ checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94"
|
|||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"lazy_static 1.4.0",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
@ -256,7 +305,7 @@ checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278"
|
|||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
"lazy_static",
|
||||
"lazy_static 1.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -292,7 +341,7 @@ dependencies = [
|
|||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"regex 1.5.4",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
|
@ -327,6 +376,30 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fftw-src"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08962470ab0e91e74ec7d338c8731476c28ed4e503a3080b0f001692e395a7c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cc",
|
||||
"fs_extra",
|
||||
"ftp",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fftw-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8e3951d695cc2f17610cd041e87ebc15078d1af5eb8c6be77921381fc98b3fd"
|
||||
dependencies = [
|
||||
"fftw-src",
|
||||
"libc",
|
||||
"num-complex 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.20"
|
||||
|
@ -339,6 +412,23 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
||||
|
||||
[[package]]
|
||||
name = "ftp"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "542951aad0071952c27409e3bd7cb62d1a3ad419c4e7314106bf994e0083ad5d"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static 0.1.16",
|
||||
"regex 0.1.80",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
|
@ -381,6 +471,15 @@ version = "0.9.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.18"
|
||||
|
@ -424,6 +523,22 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -449,7 +564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"winapi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -476,6 +591,15 @@ dependencies = [
|
|||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.0"
|
||||
|
@ -558,10 +682,21 @@ version = "5.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"memchr 2.4.0",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.0"
|
||||
|
@ -601,6 +736,17 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
|
@ -800,21 +946,40 @@ dependencies = [
|
|||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"lazy_static 1.4.0",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "0.1.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
|
||||
dependencies = [
|
||||
"aho-corasick 0.5.3",
|
||||
"memchr 0.1.11",
|
||||
"regex-syntax 0.3.9",
|
||||
"thread_local",
|
||||
"utf8-ranges",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"aho-corasick 0.7.18",
|
||||
"memchr 2.4.0",
|
||||
"regex-syntax 0.6.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
|
@ -902,6 +1067,24 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3ff2f71c82567c565ba4b3009a9350a96a7269eaa4001ebedae926230bc2254"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb076d8b589fde927ec90d05920d610554ca3a4d9dddb177481cadd071a19c2e"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.72"
|
||||
|
@ -942,6 +1125,35 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread-id"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
|
||||
dependencies = [
|
||||
"kernel32-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
|
||||
dependencies = [
|
||||
"thread-id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "transpose"
|
||||
version = "0.2.1"
|
||||
|
@ -964,12 +1176,24 @@ version = "0.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.12"
|
||||
|
@ -988,6 +1212,12 @@ version = "0.10.2+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
@ -998,6 +1228,12 @@ dependencies = [
|
|||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
@ -1010,7 +1246,7 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1026,7 +1262,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9c83dc9b784d252127720168abd71ea82bf8c3d96b17dc565b5e2a02854f2b27"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bzip2",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
[package]
|
||||
name = "bliss-audio"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
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"]
|
||||
keywords = ["audio", "analysis", "MIR", "playlist", "similarity"]
|
||||
readme = "README.md"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
@ -41,4 +41,6 @@ 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"
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
|
|
|
@ -10,7 +10,7 @@ fn main() {
|
|||
let args: Vec<String> = env::args().skip(1).collect();
|
||||
for path in &args {
|
||||
match Song::new(&path) {
|
||||
Ok(song) => println!("{}: {:?}", path, song.analysis,),
|
||||
Ok(song) => println!("{}: {:?}", path, song.analysis),
|
||||
Err(e) => println!("{}: {}", path, e),
|
||||
}
|
||||
}
|
||||
|
|
49
src/lib.rs
49
src/lib.rs
|
@ -1,3 +1,5 @@
|
|||
//! # bliss audio library
|
||||
//!
|
||||
//! bliss is a library for making "smart" audio playlists.
|
||||
//!
|
||||
//! The core of the library is the `Song` object, which relates to a
|
||||
|
@ -17,6 +19,49 @@
|
|||
//! 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)
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ## Analyze & compute the distance between two songs
|
||||
//! ```no_run
|
||||
//! use bliss_audio::{BlissError, Song};
|
||||
//!
|
||||
//! fn main() -> Result<(), BlissError> {
|
||||
//! let song1 = Song::new("/path/to/song1")?;
|
||||
//! let song2 = Song::new("/path/to/song2")?;
|
||||
//!
|
||||
//! println!("Distance between song1 and song2 is {}", song1.distance(&song2));
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Make a playlist from a song
|
||||
//! ```no_run
|
||||
//! use bliss_audio::{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 first_song = songs.first().unwrap().to_owned();
|
||||
//!
|
||||
//! songs.sort_by_cached_key(|song| n32(first_song.distance(&song)));
|
||||
//! println!(
|
||||
//! "Playlist is: {:?}",
|
||||
//! songs
|
||||
//! .iter()
|
||||
//! .map(|song| &song.path)
|
||||
//! .collect::<Vec<&String>>()
|
||||
//! );
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
#![cfg_attr(feature = "bench", feature(test))]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(missing_doc_code_examples)]
|
||||
|
@ -35,13 +80,13 @@ extern crate num_cpus;
|
|||
extern crate serde;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use song::Song;
|
||||
pub use library::Library;
|
||||
pub use song::{Analysis, AnalysisIndex, Song};
|
||||
|
||||
const CHANNELS: u16 = 1;
|
||||
const SAMPLE_RATE: u32 = 22050;
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
#[derive(Error, Clone, Debug, PartialEq)]
|
||||
/// Umbrella type for bliss error types
|
||||
pub enum BlissError {
|
||||
#[error("error happened while decoding file – {0}")]
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
//! Module containing the Library trait, useful to get started to implement
|
||||
//! a plug-in for an audio player.
|
||||
use crate::{BlissError, Song};
|
||||
use log::{error, info, warn};
|
||||
use ndarray::{arr1, Array};
|
||||
use log::{debug, error, info};
|
||||
use noisy_float::prelude::*;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
@ -43,18 +42,16 @@ pub trait Library {
|
|||
first_song: Song,
|
||||
playlist_length: usize,
|
||||
) -> Result<Vec<Song>, BlissError> {
|
||||
let analysis_current_song = arr1(&first_song.analysis.to_vec());
|
||||
let analysis_current_song = first_song.analysis;
|
||||
let mut songs = self.get_stored_songs()?;
|
||||
let m = Array::eye(first_song.analysis.len());
|
||||
songs.sort_by_cached_key(|song| {
|
||||
n32((arr1(&song.analysis) - &analysis_current_song)
|
||||
.dot(&m)
|
||||
.dot(&(arr1(&song.analysis) - &analysis_current_song)))
|
||||
});
|
||||
Ok(songs
|
||||
songs.sort_by_cached_key(|song| n32(analysis_current_song.distance(&song.analysis)));
|
||||
|
||||
let playlist = songs
|
||||
.into_iter()
|
||||
.take(playlist_length)
|
||||
.collect::<Vec<Song>>())
|
||||
.collect::<Vec<Song>>();
|
||||
debug!("Playlist created: {:?}", playlist);
|
||||
Ok(playlist)
|
||||
}
|
||||
|
||||
/// Analyze and store songs in `paths`, using `store_song` and
|
||||
|
@ -103,11 +100,14 @@ pub trait Library {
|
|||
info!("Analyzed and stored song '{}' successfully.", song.path)
|
||||
}
|
||||
Err(e) => {
|
||||
self.store_error_song(path.to_string(), e)
|
||||
self.store_error_song(path.to_string(), e.to_owned())
|
||||
.unwrap_or_else(|e| {
|
||||
error!("Error while storing errored song '{}': {}", path, e)
|
||||
});
|
||||
warn!("Analysis of song '{}' failed. Storing error.", path)
|
||||
error!(
|
||||
"Analysis of song '{}': {} failed. Error has been stored.",
|
||||
path, e
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,7 @@ pub trait Library {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::song::Analysis;
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestLibrary {
|
||||
|
@ -247,7 +248,7 @@ mod test {
|
|||
let test_library = FailingLibrary {};
|
||||
let song = Song {
|
||||
path: String::from("path-to-first"),
|
||||
analysis: vec![0., 0., 0.],
|
||||
analysis: Analysis::new([0.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -304,11 +305,6 @@ mod test {
|
|||
String::from("./data/white_noise.flac"),
|
||||
],
|
||||
);
|
||||
|
||||
test_library
|
||||
.internal_storage
|
||||
.iter()
|
||||
.for_each(|x| assert!(x.analysis.len() > 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -316,25 +312,25 @@ mod test {
|
|||
let mut test_library = TestLibrary::default();
|
||||
let first_song = Song {
|
||||
path: String::from("path-to-first"),
|
||||
analysis: vec![0., 0., 0.],
|
||||
analysis: Analysis::new([0.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let second_song = Song {
|
||||
path: String::from("path-to-second"),
|
||||
analysis: vec![0.1, 0., 0.],
|
||||
analysis: Analysis::new([0.1; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let third_song = Song {
|
||||
path: String::from("path-to-third"),
|
||||
analysis: vec![10., 11., 10.],
|
||||
analysis: Analysis::new([10.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let fourth_song = Song {
|
||||
path: String::from("path-to-fourth"),
|
||||
analysis: vec![20., 21., 20.],
|
||||
analysis: Analysis::new([20.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -355,19 +351,19 @@ mod test {
|
|||
let mut test_library = TestLibrary::default();
|
||||
let first_song = Song {
|
||||
path: String::from("path-to-first"),
|
||||
analysis: vec![0., 0., 0.],
|
||||
analysis: Analysis::new([0.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let second_song = Song {
|
||||
path: String::from("path-to-second"),
|
||||
analysis: vec![0.1, 0., 0.],
|
||||
analysis: Analysis::new([0.1; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let third_song = Song {
|
||||
path: String::from("path-to-third"),
|
||||
analysis: vec![10., 11., 10.],
|
||||
analysis: Analysis::new([10.; 20]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
236
src/song.rs
236
src/song.rs
|
@ -19,6 +19,7 @@ use crate::temporal::BPMDesc;
|
|||
use crate::timbral::{SpectralDesc, ZeroCrossingRateDesc};
|
||||
use crate::{BlissError, SAMPLE_RATE};
|
||||
use ::log::warn;
|
||||
use core::ops::Index;
|
||||
use crossbeam::thread;
|
||||
use ffmpeg_next::codec::threading::{Config, Type as ThreadingType};
|
||||
use ffmpeg_next::software::resampling::context::Context;
|
||||
|
@ -30,10 +31,14 @@ use ffmpeg_next::util::format::sample::{Sample, Type};
|
|||
use ffmpeg_next::util::frame::audio::Audio;
|
||||
use ffmpeg_next::util::log;
|
||||
use ffmpeg_next::util::log::level::Level;
|
||||
use ndarray::{arr1, Array};
|
||||
use ndarray::{arr1, Array, Array1};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::thread as std_thread;
|
||||
use strum::{EnumCount, IntoEnumIterator};
|
||||
use strum_macros::{EnumCount, EnumIter};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Default, Debug, PartialEq, Clone)]
|
||||
|
@ -52,14 +57,126 @@ pub struct Song {
|
|||
pub track_number: String,
|
||||
/// Song's genre, read from the metadata (`""` if empty)
|
||||
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.
|
||||
/// bliss analysis results
|
||||
pub analysis: Analysis,
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumIter, EnumCount)]
|
||||
/// Indexes different fields of an [Analysis](Song::analysis).
|
||||
///
|
||||
/// * Example:
|
||||
/// ```no_run
|
||||
/// use bliss_audio::{AnalysisIndex, BlissError, Song};
|
||||
///
|
||||
/// fn main() -> Result<(), BlissError> {
|
||||
/// let song = Song::new("path/to/song")?;
|
||||
/// println!("{}", song.analysis[AnalysisIndex::Tempo]);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Prints the tempo value of an analysis.
|
||||
///
|
||||
/// Note that this should mostly be used for debugging / distance metric
|
||||
/// customization purposes.
|
||||
#[allow(missing_docs)]
|
||||
pub enum AnalysisIndex {
|
||||
Tempo,
|
||||
Zcr,
|
||||
MeanSpectralCentroid,
|
||||
StdDeviationSpectralCentroid,
|
||||
MeanSpectralRolloff,
|
||||
StdDeviationSpectralRolloff,
|
||||
MeanSpectralFlatness,
|
||||
StdDeviationSpectralFlatness,
|
||||
MeanLoudness,
|
||||
StdDeviationLoudness,
|
||||
Chroma1,
|
||||
Chroma2,
|
||||
Chroma3,
|
||||
Chroma4,
|
||||
Chroma5,
|
||||
Chroma6,
|
||||
Chroma7,
|
||||
Chroma8,
|
||||
Chroma9,
|
||||
Chroma10,
|
||||
}
|
||||
const NUMBER_FEATURES: usize = AnalysisIndex::COUNT;
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
/// Object holding the results of the song's analysis.
|
||||
///
|
||||
/// Only use it if you want to have an in-depth look of what is
|
||||
/// happening behind the scene, or make a distance metric yourself.
|
||||
///
|
||||
/// Under the hood, it is just an array of f32 holding different numeric
|
||||
/// features.
|
||||
///
|
||||
/// For more info on the different features, build the
|
||||
/// documentation with private items included using
|
||||
/// `cargo doc --document-private-items`, and / or read up
|
||||
/// [this document](https://lelele.io/thesis.pdf), that contains a description
|
||||
/// on most of the features, except the chroma ones, which are documented
|
||||
/// directly in this code.
|
||||
pub struct Analysis {
|
||||
internal_analysis: [f32; NUMBER_FEATURES],
|
||||
}
|
||||
|
||||
impl Index<AnalysisIndex> for Analysis {
|
||||
type Output = f32;
|
||||
|
||||
fn index(&self, index: AnalysisIndex) -> &f32 {
|
||||
&self.internal_analysis[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Analysis {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut debug_struct = f.debug_struct("Analysis");
|
||||
for feature in AnalysisIndex::iter() {
|
||||
debug_struct.field(&format!("{:?}", feature), &self[feature]);
|
||||
}
|
||||
debug_struct.finish()?;
|
||||
f.write_str(&format!(" /* {:?} */", &self.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Analysis {
|
||||
pub(crate) fn new(analysis: [f32; NUMBER_FEATURES]) -> Analysis {
|
||||
Analysis {
|
||||
internal_analysis: analysis,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an ndarray `arr1`.
|
||||
///
|
||||
/// All the numbers are between -1 and 1.
|
||||
pub analysis: Vec<f32>,
|
||||
/// Particularly useful if you want to make a custom distance metric.
|
||||
pub fn to_arr1(&self) -> Array1<f32> {
|
||||
arr1(&self.internal_analysis)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn to_vec(&self) -> Vec<f32> {
|
||||
self.internal_analysis.to_vec()
|
||||
}
|
||||
|
||||
/// Return the [euclidean
|
||||
/// distance](https://en.wikipedia.org/wiki/Euclidean_distance#Higher_dimensions)
|
||||
/// between two analysis.
|
||||
///
|
||||
/// Note that it is usually easier to just use [`song.distance(song2)`](Song::distance)
|
||||
/// (which calls this function in turn).
|
||||
pub fn distance(&self, other: &Self) -> f32 {
|
||||
let a1 = self.to_arr1();
|
||||
let a2 = other.to_arr1();
|
||||
// Could be any square symmetric positive semi-definite matrix;
|
||||
// just no metric learning has been done yet.
|
||||
// See https://lelele.io/thesis.pdf chapter 4.
|
||||
let m = Array::eye(NUMBER_FEATURES);
|
||||
|
||||
(self.to_arr1() - &a2).dot(&m).dot(&(&a1 - &a2)).sqrt()
|
||||
}
|
||||
}
|
||||
|
||||
impl Song {
|
||||
|
@ -71,14 +188,7 @@ impl Song {
|
|||
/// (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());
|
||||
// Could be any square symmetric positive semi-definite matrix;
|
||||
// just no metric learning has been done yet.
|
||||
// See https://lelele.io/thesis.pdf chapter 4.
|
||||
let m = Array::eye(self.analysis.len());
|
||||
|
||||
(arr1(&self.analysis) - &a2).dot(&m).dot(&(&a1 - &a2))
|
||||
self.analysis.distance(&other.analysis)
|
||||
}
|
||||
|
||||
/// Returns a decoded Song given a file path, or an error if the song
|
||||
|
@ -122,7 +232,7 @@ impl Song {
|
|||
* Useful in the rare cases where the full song is not
|
||||
* completely available.
|
||||
**/
|
||||
fn analyse(sample_array: Vec<f32>) -> Result<Vec<f32>, BlissError> {
|
||||
fn analyse(sample_array: Vec<f32>) -> Result<Analysis, BlissError> {
|
||||
let largest_window = vec![
|
||||
BPMDesc::WINDOW_SIZE,
|
||||
ChromaDesc::WINDOW_SIZE,
|
||||
|
@ -207,7 +317,14 @@ impl Song {
|
|||
result.extend_from_slice(&flatness);
|
||||
result.extend_from_slice(&loudness);
|
||||
result.extend_from_slice(&chroma);
|
||||
Ok(result)
|
||||
let array: [f32; NUMBER_FEATURES] = result.try_into().map_err(|_| {
|
||||
BlissError::AnalysisError(
|
||||
"Too many or too little features were provided at the end of
|
||||
the analysis."
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
Ok(Analysis::new(array))
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
@ -226,9 +343,7 @@ impl Song {
|
|||
let stream = format
|
||||
.streams()
|
||||
.find(|s| s.codec().medium() == ffmpeg::media::Type::Audio)
|
||||
.ok_or_else(|| BlissError::DecodingError(String::from(
|
||||
"No audio stream found.",
|
||||
)))?;
|
||||
.ok_or_else(|| BlissError::DecodingError(String::from("No audio stream found.")))?;
|
||||
stream.codec().set_threading(Config {
|
||||
kind: ThreadingType::Frame,
|
||||
count: 0,
|
||||
|
@ -477,7 +592,7 @@ mod tests {
|
|||
-0.9820945,
|
||||
-0.95968974,
|
||||
];
|
||||
for (x, y) in song.analysis.iter().zip(expected_analysis) {
|
||||
for (x, y) in song.analysis.to_vec().iter().zip(expected_analysis) {
|
||||
assert!(0.01 > (x - y).abs());
|
||||
}
|
||||
}
|
||||
|
@ -582,48 +697,28 @@ mod tests {
|
|||
#[test]
|
||||
fn test_analysis_distance() {
|
||||
let mut a = Song::default();
|
||||
a.analysis = vec![
|
||||
0.37860596,
|
||||
-0.75483,
|
||||
-0.85036564,
|
||||
-0.6326486,
|
||||
-0.77610075,
|
||||
0.27126348,
|
||||
-1.,
|
||||
0.,
|
||||
1.,
|
||||
];
|
||||
a.analysis = Analysis::new([
|
||||
0.16391512, 0.11326739, 0.96868552, 0.8353934, 0.49867523, 0.76532606, 0.63448005,
|
||||
0.82506196, 0.71457147, 0.62395476, 0.69680329, 0.9855766, 0.41369333, 0.13900452,
|
||||
0.68001012, 0.11029723, 0.97192943, 0.57727861, 0.07994821, 0.88993185,
|
||||
]);
|
||||
|
||||
let mut b = Song::default();
|
||||
b.analysis = vec![
|
||||
0.31255,
|
||||
0.15483,
|
||||
-0.15036564,
|
||||
-0.0326486,
|
||||
-0.87610075,
|
||||
-0.27126348,
|
||||
1.,
|
||||
0.,
|
||||
1.,
|
||||
];
|
||||
assert_eq!(a.distance(&b), 5.986180)
|
||||
b.analysis = Analysis::new([
|
||||
0.5075758, 0.36440256, 0.28888011, 0.43032829, 0.62387977, 0.61894916, 0.99676086,
|
||||
0.11913155, 0.00640396, 0.15943407, 0.33829514, 0.34947174, 0.82927523, 0.18987604,
|
||||
0.54437275, 0.22076826, 0.91232151, 0.29233168, 0.32846024, 0.04522147,
|
||||
]);
|
||||
assert_eq!(a.distance(&b), 1.9469079)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_analysis_distance_indiscernible() {
|
||||
let mut a = Song::default();
|
||||
a.analysis = vec![
|
||||
0.37860596,
|
||||
-0.75483,
|
||||
-0.85036564,
|
||||
-0.6326486,
|
||||
-0.77610075,
|
||||
0.27126348,
|
||||
-1.,
|
||||
0.,
|
||||
1.,
|
||||
];
|
||||
|
||||
a.analysis = Analysis::new([
|
||||
1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19.,
|
||||
20.,
|
||||
]);
|
||||
assert_eq!(a.distance(&a), 0.)
|
||||
}
|
||||
|
||||
|
@ -640,6 +735,33 @@ mod tests {
|
|||
BlissError::DecodingError(String::from("No audio stream found.")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_index_analysis() {
|
||||
let song = Song::new("data/s16_mono_22_5kHz.flac").unwrap();
|
||||
assert_eq!(song.analysis[AnalysisIndex::Tempo], 0.3846389);
|
||||
assert_eq!(song.analysis[AnalysisIndex::Chroma10], -0.95968974);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_analysis() {
|
||||
let song = Song::new("data/s16_mono_22_5kHz.flac").unwrap();
|
||||
assert_eq!(
|
||||
"Analysis { Tempo: 0.3846389, Zcr: -0.849141, MeanSpectralCentroid: \
|
||||
-0.75481045, StdDeviationSpectralCentroid: -0.8790748, MeanSpectralR\
|
||||
olloff: -0.63258266, StdDeviationSpectralRolloff: -0.7258959, MeanSp\
|
||||
ectralFlatness: -0.7757379, StdDeviationSpectralFlatness: -0.8146726\
|
||||
, MeanLoudness: 0.2716726, StdDeviationLoudness: 0.25779057, Chroma1\
|
||||
: -0.35661936, Chroma2: -0.63578653, Chroma3: -0.29593682, Chroma4: \
|
||||
0.06421304, Chroma5: 0.21852458, Chroma6: -0.581239, Chroma7: -0.946\
|
||||
6835, Chroma8: -0.9481153, Chroma9: -0.9820945, Chroma10: -0.95968974 } \
|
||||
/* [0.3846389, -0.849141, -0.75481045, -0.8790748, -0.63258266, -0.\
|
||||
7258959, -0.7757379, -0.8146726, 0.2716726, 0.25779057, -0.35661936, \
|
||||
-0.63578653, -0.29593682, 0.06421304, 0.21852458, -0.581239, -0.946\
|
||||
6835, -0.9481153, -0.9820945, -0.95968974] */",
|
||||
format!("{:?}", song.analysis),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "bench", test))]
|
||||
|
|
Loading…
Reference in a new issue