Compare commits

...

1 Commits

Author SHA1 Message Date
Paul Arzelier
efcdd1617e Add a "disable-ffmpeg" feature 2023-12-24 17:22:00 +01:00
9 changed files with 74 additions and 3 deletions

View File

@ -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"]

View File

@ -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()

View File

@ -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![

View File

@ -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",

View File

@ -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();

View File

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

View File

@ -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();

View File

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

View File

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