Compare commits
1 Commits
master
...
make-ffmpe
Author | SHA1 | Date | |
---|---|---|---|
|
efcdd1617e |
13
Cargo.toml
13
Cargo.toml
|
@ -28,6 +28,9 @@ update-aubio-bindings = ["bliss-audio-aubio-rs/bindgen"]
|
|||
python-bindings = ["bliss-audio-aubio-rs/fftw3"]
|
||||
# Enable the benchmarks with `cargo +nightly bench --features=bench`
|
||||
bench = []
|
||||
# Disable song decoding through ffmpeg - only use if you know what you
|
||||
# are doing and want to decode songs yourself
|
||||
disable-ffmpeg = []
|
||||
library = [
|
||||
"serde", "dep:rusqlite", "dep:dirs", "dep:tempdir",
|
||||
"dep:anyhow", "dep:serde_ini", "dep:serde_json",
|
||||
|
@ -84,4 +87,12 @@ required-features = ["library"]
|
|||
|
||||
[[example]]
|
||||
name = "playlist"
|
||||
required-features = ["serde"]
|
||||
required-features = ["serde", "ffmpeg"]
|
||||
|
||||
[[example]]
|
||||
name = "distance"
|
||||
required-features = ["ffmpeg"]
|
||||
|
||||
[[example]]
|
||||
name = "analyze"
|
||||
required-features = ["ffmpeg"]
|
||||
|
|
|
@ -434,6 +434,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_chroma_desc() {
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
|
||||
|
@ -456,6 +457,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_chroma_stft_decode() {
|
||||
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
|
||||
.unwrap()
|
||||
|
@ -489,6 +491,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_estimate_tuning_decode() {
|
||||
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
|
||||
.unwrap()
|
||||
|
@ -606,6 +609,7 @@ mod bench {
|
|||
}
|
||||
|
||||
#[bench]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn bench_chroma_desc(b: &mut Bencher) {
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
|
||||
|
@ -617,6 +621,7 @@ mod bench {
|
|||
}
|
||||
|
||||
#[bench]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn bench_chroma_stft(b: &mut Bencher) {
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
|
||||
|
@ -628,6 +633,7 @@ mod bench {
|
|||
}
|
||||
|
||||
#[bench]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn bench_chroma_stft_decode(b: &mut Bencher) {
|
||||
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
|
||||
.unwrap()
|
||||
|
|
|
@ -49,6 +49,7 @@ impl BlissCue {
|
|||
/// Each returned [Song] has a populated [cue_info](Song::cue_info) object, that can be
|
||||
/// be used to retrieve which CUE sheet was used to extract it, as well
|
||||
/// as the corresponding audio file.
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
pub fn songs_from_path<P: AsRef<Path>>(path: P) -> BlissResult<Vec<BlissResult<Song>>> {
|
||||
let cue = BlissCue::from_path(&path)?;
|
||||
let cue_files = cue.files();
|
||||
|
@ -86,6 +87,7 @@ impl BlissCue {
|
|||
}
|
||||
|
||||
// List all BlissCueFile from a BlissCue.
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn files(&self) -> Vec<BlissResult<BlissCueFile>> {
|
||||
let mut cue_files = Vec::new();
|
||||
for cue_file in self.cue.files.iter() {
|
||||
|
@ -196,6 +198,7 @@ mod tests {
|
|||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_empty_cue() {
|
||||
let songs = BlissCue::songs_from_path("data/empty.cue").unwrap();
|
||||
let error = songs[0].to_owned().unwrap_err();
|
||||
|
@ -206,6 +209,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_cue_analysis() {
|
||||
let songs = BlissCue::songs_from_path("data/testcue.cue").unwrap();
|
||||
let expected = vec![
|
||||
|
|
|
@ -23,9 +23,13 @@
|
|||
//! an example of how the [Library] struct works, and a real-life demo of bliss
|
||||
//! implemented for [MPD](https://www.musicpd.org/).
|
||||
//!
|
||||
#![cfg_attr(
|
||||
not(feature = "disable-ffmpeg"),
|
||||
doc = r##"
|
||||
//! # Examples
|
||||
//!
|
||||
//! ### Analyze & compute the distance between two songs
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use bliss_audio::{BlissResult, Song};
|
||||
//!
|
||||
|
@ -62,6 +66,8 @@
|
|||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
"##
|
||||
)]
|
||||
#![cfg_attr(feature = "bench", feature(test))]
|
||||
#![warn(missing_docs)]
|
||||
mod chroma;
|
||||
|
@ -152,6 +158,7 @@ pub type BlissResult<T> = Result<T, BlissError>;
|
|||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
pub fn analyze_paths<P: Into<PathBuf>, F: IntoIterator<Item = P>>(
|
||||
paths: F,
|
||||
) -> mpsc::IntoIter<(PathBuf, BlissResult<Song>)> {
|
||||
|
@ -199,6 +206,7 @@ pub fn analyze_paths<P: Into<PathBuf>, F: IntoIterator<Item = P>>(
|
|||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
pub fn analyze_paths_with_cores<P: Into<PathBuf>, F: IntoIterator<Item = P>>(
|
||||
paths: F,
|
||||
number_cores: NonZeroUsize,
|
||||
|
@ -270,6 +278,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_analyze_paths() {
|
||||
let paths = vec![
|
||||
"./data/s16_mono_22_5kHz.flac",
|
||||
|
|
|
@ -67,6 +67,7 @@ mod tests {
|
|||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_loudness() {
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
let mut loudness_desc = LoudnessDesc::default();
|
||||
|
|
35
src/song.rs
35
src/song.rs
|
@ -8,6 +8,7 @@
|
|||
//! a look at Library is instead recommended.
|
||||
|
||||
extern crate crossbeam;
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
extern crate ffmpeg_next as ffmpeg;
|
||||
extern crate ndarray;
|
||||
|
||||
|
@ -24,13 +25,21 @@ use crate::{CHANNELS, FEATURES_VERSION};
|
|||
use ::log::warn;
|
||||
use core::ops::Index;
|
||||
use crossbeam::thread;
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
use ffmpeg_next::codec::threading::{Config, Type as ThreadingType};
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
use ffmpeg_next::util::channel_layout::ChannelLayout;
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
use ffmpeg_next::util::error::Error;
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
use ffmpeg_next::util::error::EINVAL;
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
use ffmpeg_next::util::format::sample::{Sample, Type};
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
use ffmpeg_next::util::frame::audio::Audio;
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
use ffmpeg_next::util::log;
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
use ffmpeg_next::util::log::level::Level;
|
||||
use ffmpeg_next::{media, util};
|
||||
use ndarray::{arr1, Array1};
|
||||
|
@ -84,6 +93,9 @@ pub struct Song {
|
|||
#[derive(Debug, EnumIter, EnumCount)]
|
||||
/// Indexes different fields of an [Analysis](Song::analysis).
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(feature = "disable-ffmpeg"),
|
||||
doc = r##"
|
||||
/// * Example:
|
||||
/// ```no_run
|
||||
/// use bliss_audio::{AnalysisIndex, BlissResult, Song};
|
||||
|
@ -94,7 +106,8 @@ pub struct Song {
|
|||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
"##
|
||||
)]
|
||||
/// Prints the tempo value of an analysis.
|
||||
///
|
||||
/// Note that this should mostly be used for debugging / distance metric
|
||||
|
@ -296,6 +309,7 @@ impl Song {
|
|||
/// 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.
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> BlissResult<Self> {
|
||||
let raw_song = Song::decode(path.as_ref())?;
|
||||
|
||||
|
@ -437,6 +451,7 @@ impl Song {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
pub(crate) fn decode(path: &Path) -> BlissResult<InternalSong> {
|
||||
ffmpeg::init().map_err(|e| {
|
||||
BlissError::DecodingError(format!(
|
||||
|
@ -658,6 +673,7 @@ pub(crate) struct InternalSong {
|
|||
pub sample_array: Vec<f32>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn resample_frame(
|
||||
rx: Receiver<Audio>,
|
||||
in_codec_format: Sample,
|
||||
|
@ -727,6 +743,7 @@ fn resample_frame(
|
|||
Ok(sample_array)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn push_to_sample_array(frame: &ffmpeg::frame::Audio, sample_array: &mut Vec<f32>) {
|
||||
if frame.samples() == 0 {
|
||||
return;
|
||||
|
@ -772,6 +789,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_analyze() {
|
||||
let song = Song::from_path(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
let expected_analysis = vec![
|
||||
|
@ -802,6 +820,7 @@ mod tests {
|
|||
assert_eq!(FEATURES_VERSION, song.features_version);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn _test_decode(path: &Path, expected_hash: &[u8]) {
|
||||
let song = Song::decode(path).unwrap();
|
||||
let mut hasher = Ripemd160::new();
|
||||
|
@ -813,6 +832,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_tags() {
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
assert_eq!(song.artist, Some(String::from("David TMX")));
|
||||
|
@ -830,6 +850,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_empty_tags() {
|
||||
let song = Song::decode(Path::new("data/no_tags.flac")).unwrap();
|
||||
assert_eq!(song.artist, None);
|
||||
|
@ -840,6 +861,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_resample_multi() {
|
||||
let path = Path::new("data/s32_stereo_44_1_kHz.flac");
|
||||
let expected_hash = [
|
||||
|
@ -850,6 +872,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_resample_stereo() {
|
||||
let path = Path::new("data/s16_stereo_22_5kHz.flac");
|
||||
let expected_hash = [
|
||||
|
@ -860,6 +883,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_decode_mono() {
|
||||
let path = Path::new("data/s16_mono_22_5kHz.flac");
|
||||
// Obtained through
|
||||
|
@ -873,6 +897,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_decode_mp3() {
|
||||
let path = Path::new("data/s32_stereo_44_1_kHz.mp3");
|
||||
// Obtained through
|
||||
|
@ -886,12 +911,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_dont_panic_no_channel_layout() {
|
||||
let path = Path::new("data/no_channel.wav");
|
||||
Song::decode(&path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_decode_right_capacity_vec() {
|
||||
let path = Path::new("data/s16_mono_22_5kHz.flac");
|
||||
let song = Song::decode(&path).unwrap();
|
||||
|
@ -945,6 +972,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_decode_errors() {
|
||||
assert_eq!(
|
||||
Song::decode(Path::new("nonexistent")).unwrap_err(),
|
||||
|
@ -961,6 +989,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_index_analysis() {
|
||||
let song = Song::from_path("data/s16_mono_22_5kHz.flac").unwrap();
|
||||
assert_eq!(song.analysis[AnalysisIndex::Tempo], 0.3846389);
|
||||
|
@ -968,6 +997,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_decode_wav() {
|
||||
let expected_hash = [
|
||||
0xf0, 0xe0, 0x85, 0x4e, 0xf6, 0x53, 0x76, 0xfa, 0x7a, 0xa5, 0x65, 0x76, 0xf9, 0xe1,
|
||||
|
@ -977,6 +1007,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_debug_analysis() {
|
||||
let song = Song::from_path("data/s16_mono_22_5kHz.flac").unwrap();
|
||||
assert_eq!(
|
||||
|
@ -1097,7 +1128,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "bench", test))]
|
||||
#[cfg(all(feature = "bench", not(feature = "disable-ffmpeg"), test))]
|
||||
mod bench {
|
||||
extern crate test;
|
||||
use crate::Song;
|
||||
|
|
|
@ -98,6 +98,7 @@ mod tests {
|
|||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_tempo_real() {
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
let mut tempo_desc = BPMDesc::new(SAMPLE_RATE).unwrap();
|
||||
|
|
|
@ -280,6 +280,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_zcr() {
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
let mut zcr_desc = ZeroCrossingRateDesc::default();
|
||||
|
@ -290,6 +291,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_spectral_flatness_boundaries() {
|
||||
let mut spectral_desc = SpectralDesc::new(10).unwrap();
|
||||
let chunk = vec![0.; 1024];
|
||||
|
@ -319,6 +321,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_spectral_flatness() {
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).unwrap();
|
||||
|
@ -337,6 +340,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_spectral_roll_off_boundaries() {
|
||||
let mut spectral_desc = SpectralDesc::new(10).unwrap();
|
||||
let chunk = vec![0.; 512];
|
||||
|
@ -365,6 +369,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_spectral_roll_off() {
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).unwrap();
|
||||
|
@ -383,6 +388,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_spectral_centroid() {
|
||||
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
|
||||
let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE).unwrap();
|
||||
|
@ -401,6 +407,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_spectral_centroid_boundaries() {
|
||||
let mut spectral_desc = SpectralDesc::new(10).unwrap();
|
||||
let chunk = vec![0.; 512];
|
||||
|
|
|
@ -492,6 +492,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "disable-ffmpeg"))]
|
||||
fn test_compute_stft() {
|
||||
let file = File::open("data/librosa-stft.npy").unwrap();
|
||||
let expected_stft = Array2::<f32>::read_npy(file).unwrap().mapv(|x| x as f64);
|
||||
|
|
Loading…
Reference in New Issue
Block a user