Skip to content

Commit

Permalink
Continue the optional ffmpeg feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Polochon-street committed Apr 2, 2024
1 parent 4e3e51d commit 1b39567
Show file tree
Hide file tree
Showing 17 changed files with 761 additions and 688 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ jobs:
run: cargo +nightly-2024-01-16 bench --verbose --features=bench --no-run
- name: Build examples
run: cargo build --examples --verbose --features=serde,library
- name: Build without ffmpeg
run: cargo build --verbose --no-default-features


build-test-lint-windows:
name: Windows - build, test and lint
Expand Down
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ask questions if you want to tackle an item.

- Split out ffmpeg (see https://github.com/Polochon-street/bliss-rs/issues/63 and https://users.rust-lang.org/t/proper-way-to-abstract-a-third-party-provider/107076/8)
- Make ffmpeg an optional (but default) feature
- The library trait must be Decoder-agnostic, and not depend on FFmpeg
- Make the tests that don't need it not dependent of ffmpeg
- Make the Song::from_path a trait that is by default implemented with the
ffmpeg feature (so you can theoretically implement the library trait without ffmpeg)
Expand Down
2 changes: 1 addition & 1 deletion ci_check.sh
Original file line number Diff line number Diff line change
@@ -1 +1 @@
cargo fmt -- --check && cargo clippy --examples --features=serde -- -D warnings && cargo build --verbose && cargo test --verbose && cargo test --verbose --examples && cargo +nightly-2023-02-16 bench --verbose --features=bench --no-run && cargo build --examples --verbose --features=serde
cargo fmt -- --check && cargo clippy --examples --features=serde -- -D warnings && cargo build --verbose && cargo test --verbose && cargo test --verbose --examples && cargo +nightly-2024-01-16 bench --verbose --features=bench --no-run && cargo build --examples --verbose --features=serde && cargo build --no-default-features
6 changes: 4 additions & 2 deletions examples/analyze.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use bliss_audio::Song;
#[cfg(feature = "ffmpeg")]
use bliss_audio::decoder::bliss_ffmpeg::FFmpeg as Decoder;
use bliss_audio::decoder::Decoder as DecoderTrait;
use std::env;

/**
Expand All @@ -9,7 +11,7 @@ use std::env;
fn main() {
let args: Vec<String> = env::args().skip(1).collect();
for path in &args {
match Song::from_path(path) {
match Decoder::song_from_path(path) {
Ok(song) => println!("{}: {:?}", path, song.analysis),
Err(e) => println!("{path}: {e}"),
}
Expand Down
9 changes: 6 additions & 3 deletions examples/distance.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use bliss_audio::{playlist::euclidean_distance, Song};
#[cfg(feature = "ffmpeg")]
use bliss_audio::decoder::bliss_ffmpeg::FFmpeg as Decoder;
use bliss_audio::decoder::Decoder as DecoderTrait;
use bliss_audio::playlist::euclidean_distance;
use std::env;

/**
Expand All @@ -13,8 +16,8 @@ fn main() -> Result<(), String> {
let first_path = paths.next().ok_or("Help: ./distance <song1> <song2>")?;
let second_path = paths.next().ok_or("Help: ./distance <song1> <song2>")?;

let song1 = Song::from_path(first_path).map_err(|x| x.to_string())?;
let song2 = Song::from_path(second_path).map_err(|x| x.to_string())?;
let song1 = Decoder::song_from_path(first_path).map_err(|x| x.to_string())?;
let song2 = Decoder::song_from_path(second_path).map_err(|x| x.to_string())?;

println!(
"d({:?}, {:?}) = {}",
Expand Down
7 changes: 4 additions & 3 deletions examples/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/// For simplicity's sake, this example recursively gets songs from a folder
/// to emulate an audio player library, without handling CUE files.
use anyhow::Result;
use bliss_audio::decoder::bliss_ffmpeg::FFmpeg as Decoder;
use bliss_audio::library::{AppConfigTrait, BaseConfig, Library};
use clap::{App, Arg, SubCommand};
use glob::glob;
Expand Down Expand Up @@ -68,7 +69,7 @@ trait CustomLibrary {
fn song_paths(&self) -> Result<Vec<String>>;
}

impl CustomLibrary for Library<Config> {
impl CustomLibrary for Library<Config, Decoder> {
/// Get all songs in the player library
fn song_paths(&self) -> Result<Vec<String>> {
let music_path = &self.config.music_library_path;
Expand Down Expand Up @@ -180,7 +181,7 @@ fn main() -> Result<()> {
library.analyze_paths(library.song_paths()?, true)?;
} else if let Some(sub_m) = matches.subcommand_matches("update") {
let config_path = sub_m.value_of("config-path").map(PathBuf::from);
let mut library: Library<Config> = Library::from_config_path(config_path)?;
let mut library: Library<Config, Decoder> = Library::from_config_path(config_path)?;
library.update_library(library.song_paths()?, true, true)?;
} else if let Some(sub_m) = matches.subcommand_matches("playlist") {
let song_path = sub_m.value_of("SONG_PATH").unwrap();
Expand All @@ -189,7 +190,7 @@ fn main() -> Result<()> {
.value_of("playlist-length")
.unwrap_or("20")
.parse::<usize>()?;
let library: Library<Config> = Library::from_config_path(config_path)?;
let library: Library<Config, Decoder> = Library::from_config_path(config_path)?;
let songs = library.playlist_from::<()>(&[song_path], playlist_length)?;
let song_paths = songs
.into_iter()
Expand Down
7 changes: 4 additions & 3 deletions examples/library_extra_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/// For simplicity's sake, this example recursively gets songs from a folder
/// to emulate an audio player library, without handling CUE files.
use anyhow::Result;
use bliss_audio::decoder::bliss_ffmpeg::FFmpeg as Decoder;
use bliss_audio::library::{AppConfigTrait, BaseConfig, Library};
use clap::{App, Arg, SubCommand};
use glob::glob;
Expand Down Expand Up @@ -69,7 +70,7 @@ trait CustomLibrary {
fn song_paths_info(&self) -> Result<Vec<(String, ExtraInfo)>>;
}

impl CustomLibrary for Library<Config> {
impl CustomLibrary for Library<Config, Decoder> {
/// Get all songs in the player library, along with the extra info
/// one would want to store along with each song.
fn song_paths_info(&self) -> Result<Vec<(String, ExtraInfo)>> {
Expand Down Expand Up @@ -198,7 +199,7 @@ fn main() -> Result<()> {
library.analyze_paths_extra_info(library.song_paths_info()?, true)?;
} else if let Some(sub_m) = matches.subcommand_matches("update") {
let config_path = sub_m.value_of("config-path").map(PathBuf::from);
let mut library: Library<Config> = Library::from_config_path(config_path)?;
let mut library: Library<Config, Decoder> = Library::from_config_path(config_path)?;
library.update_library_extra_info(library.song_paths_info()?, true, true)?;
} else if let Some(sub_m) = matches.subcommand_matches("playlist") {
let song_path = sub_m.value_of("SONG_PATH").unwrap();
Expand All @@ -207,7 +208,7 @@ fn main() -> Result<()> {
.value_of("playlist-length")
.unwrap_or("20")
.parse::<usize>()?;
let library: Library<Config> = Library::from_config_path(config_path)?;
let library: Library<Config, Decoder> = Library::from_config_path(config_path)?;
let songs = library.playlist_from::<ExtraInfo>(&[song_path], playlist_length)?;
let playlist = songs
.into_iter()
Expand Down
8 changes: 5 additions & 3 deletions examples/playlist.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use anyhow::Result;
use bliss_audio::decoder::bliss_ffmpeg::FFmpeg as Decoder;
use bliss_audio::decoder::Decoder as DecoderTrait;
use bliss_audio::playlist::{closest_to_songs, dedup_playlist, euclidean_distance};
use bliss_audio::{analyze_paths, Song};
use bliss_audio::Song;
use clap::{App, Arg};
use glob::glob;
use std::env;
Expand Down Expand Up @@ -56,14 +58,14 @@ fn main() -> Result<()> {
.map(|x| x.to_string_lossy().to_string())
.collect::<Vec<String>>();

let song_iterator = analyze_paths(
let song_iterator = Decoder::analyze_paths(
paths
.iter()
.filter(|p| !analyzed_paths.contains(&PathBuf::from(p)))
.map(|p| p.to_owned())
.collect::<Vec<String>>(),
);
let first_song = Song::from_path(file)?;
let first_song = Decoder::song_from_path(file)?;
let mut analyzed_songs = vec![first_song.to_owned()];
for (path, result) in song_iterator {
match result {
Expand Down
21 changes: 13 additions & 8 deletions src/chroma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,12 @@ fn chroma_stft(
mod test {
use super::*;
#[cfg(feature = "ffmpeg")]
use crate::song::decoder::bliss_ffmpeg::FFmpeg as Decoder;
use crate::song::decoder::Decoder as DecoderTrait;
#[cfg(feature = "ffmpeg")]
use crate::utils::stft;
#[cfg(feature = "ffmpeg")]
use crate::{Song, SAMPLE_RATE};
use crate::SAMPLE_RATE;
use ndarray::{arr1, arr2, Array2};
use ndarray_npy::ReadNpyExt;
use std::fs::File;
Expand Down Expand Up @@ -439,7 +442,7 @@ mod test {
#[test]
#[cfg(feature = "ffmpeg")]
fn test_chroma_desc() {
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let song = Decoder::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
chroma_desc.do_(&song.sample_array).unwrap();
let expected_values = vec![
Expand All @@ -462,7 +465,7 @@ mod test {
#[test]
#[cfg(feature = "ffmpeg")]
fn test_chroma_stft_decode() {
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
let signal = Decoder::decode(Path::new("data/s16_mono_22_5kHz.flac"))
.unwrap()
.sample_array;
let mut stft = stft(&signal, 8192, 2205);
Expand Down Expand Up @@ -496,7 +499,7 @@ mod test {
#[test]
#[cfg(feature = "ffmpeg")]
fn test_estimate_tuning_decode() {
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
let signal = Decoder::decode(Path::new("data/s16_mono_22_5kHz.flac"))
.unwrap()
.sample_array;
let stft = stft(&signal, 8192, 2205);
Expand Down Expand Up @@ -559,8 +562,10 @@ mod test {
mod bench {
extern crate test;
use super::*;
use crate::song::decoder::bliss_ffmpeg::FFmpeg as Decoder;
use crate::song::decoder::Decoder as DecoderTrait;
use crate::utils::stft;
use crate::{Song, SAMPLE_RATE};
use crate::SAMPLE_RATE;
use ndarray::{arr2, Array1, Array2};
use ndarray_npy::ReadNpyExt;
use std::fs::File;
Expand Down Expand Up @@ -614,7 +619,7 @@ mod bench {
#[bench]
#[cfg(feature = "ffmpeg")]
fn bench_chroma_desc(b: &mut Bencher) {
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let song = Decoder::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
let signal = song.sample_array;
b.iter(|| {
Expand All @@ -626,7 +631,7 @@ mod bench {
#[bench]
#[cfg(feature = "ffmpeg")]
fn bench_chroma_stft(b: &mut Bencher) {
let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let song = Decoder::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12);
let signal = song.sample_array;
b.iter(|| {
Expand All @@ -638,7 +643,7 @@ mod bench {
#[bench]
#[cfg(feature = "ffmpeg")]
fn bench_chroma_stft_decode(b: &mut Bencher) {
let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac"))
let signal = Decoder::decode(Path::new("data/s16_mono_22_5kHz.flac"))
.unwrap()
.sample_array;
let mut stft = stft(&signal, 8192, 2205);
Expand Down
29 changes: 13 additions & 16 deletions src/cue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@
//! and [CueInfo], which is a struct stored in [Song] to keep track of the CUE information
//! the song was extracted from.
#[cfg(feature = "ffmpeg")]
use crate::song::decoder::Decoder as DecoderTrait;
use crate::{Analysis, BlissError, BlissResult, Song, FEATURES_VERSION, SAMPLE_RATE};
use rcue::cue::{Cue, Track};
#[cfg(feature = "ffmpeg")]
use rcue::parser::parse_from_file;
#[cfg(feature = "ffmpeg")]
use std::path::Path;
use std::path::PathBuf;
#[cfg(feature = "ffmpeg")]
use std::time::Duration;
use std::{marker::PhantomData, path::PathBuf};

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Default, Debug, PartialEq, Eq, Clone)]
Expand All @@ -37,9 +34,10 @@ pub struct CueInfo {
/// Use either [analyze_paths](crate::analyze_paths) with CUE files or
/// [songs_from_path](BlissCue::songs_from_path) to return a list of [Song]s
/// from CUE files.
pub struct BlissCue {
pub struct BlissCue<D: ?Sized> {
cue: Cue,
cue_path: PathBuf,
decoder: PhantomData<D>,
}

#[allow(missing_docs)]
Expand All @@ -54,16 +52,15 @@ struct BlissCueFile {
audio_file_path: PathBuf,
}

impl BlissCue {
impl<D: ?Sized + DecoderTrait> BlissCue<D> {
/// Analyze songs from a CUE file, extracting individual [Song] objects
/// for each individual song.
///
/// 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(feature = "ffmpeg")]
pub fn songs_from_path<P: AsRef<Path>>(path: P) -> BlissResult<Vec<BlissResult<Song>>> {
let cue = BlissCue::from_path(&path)?;
let cue: BlissCue<D> = BlissCue::from_path(&path)?;
let cue_files = cue.files();
let mut songs = Vec::new();
for cue_file in cue_files.into_iter() {
Expand All @@ -84,7 +81,6 @@ impl BlissCue {
}

// Extract a BlissCue from a given path.
#[cfg(feature = "ffmpeg")]
fn from_path<P: AsRef<Path>>(path: P) -> BlissResult<Self> {
let cue = parse_from_file(&path.as_ref().to_string_lossy(), false).map_err(|e| {
BlissError::DecodingError(format!(
Expand All @@ -93,14 +89,14 @@ impl BlissCue {
e
))
})?;
Ok(BlissCue {
Ok(Self {
cue,
cue_path: path.as_ref().to_owned(),
decoder: PhantomData,
})
}

// List all BlissCueFile from a BlissCue.
#[cfg(feature = "ffmpeg")]
fn files(&self) -> Vec<BlissResult<BlissCueFile>> {
let mut cue_files = Vec::new();
for cue_file in self.cue.files.iter() {
Expand All @@ -114,7 +110,7 @@ impl BlissCue {
.iter()
.find(|(c, _)| c == "GENRE")
.map(|(_, v)| v.to_owned());
let raw_song = Song::decode(Path::new(&audio_file_path));
let raw_song = D::decode(Path::new(&audio_file_path));
if let Ok(song) = raw_song {
let bliss_cue_file = BlissCueFile {
sample_array: song.sample_array,
Expand All @@ -134,7 +130,6 @@ impl BlissCue {
}
}

#[cfg(feature = "ffmpeg")]
impl BlissCueFile {
fn create_song(
&self,
Expand Down Expand Up @@ -211,12 +206,14 @@ mod tests {
#[cfg(feature = "ffmpeg")]
use super::*;
#[cfg(feature = "ffmpeg")]
use crate::decoder::bliss_ffmpeg::FFmpeg;
#[cfg(feature = "ffmpeg")]
use pretty_assertions::assert_eq;

#[test]
#[cfg(feature = "ffmpeg")]
fn test_empty_cue() {
let songs = BlissCue::songs_from_path("data/empty.cue").unwrap();
let songs = BlissCue::<FFmpeg>::songs_from_path("data/empty.cue").unwrap();
let error = songs[0].to_owned().unwrap_err();
assert_eq!(
error,
Expand All @@ -227,7 +224,7 @@ mod tests {
#[test]
#[cfg(feature = "ffmpeg")]
fn test_cue_analysis() {
let songs = BlissCue::songs_from_path("data/testcue.cue").unwrap();
let songs = BlissCue::<FFmpeg>::songs_from_path("data/testcue.cue").unwrap();
let expected = vec![
Ok(Song {
path: Path::new("data/testcue.cue/CUE_TRACK001").to_path_buf(),
Expand Down
Loading

0 comments on commit 1b39567

Please sign in to comment.