diff --git a/CHANGELOG.md b/CHANGELOG.md index 67e6c5c..f08ebc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## bliss 0.3.0 +* Changed `Song.path` from `String` to `PathBuf`. +* Made `Song` metadata (artist, album, etc) `Option`s. +* Added a `BlissResult` error type. + ## bliss 0.2.6 * Fixed an allocation bug in Song::decode that potentially caused segfaults. diff --git a/Cargo.lock b/Cargo.lock index 9b40667..7fd34f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,7 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bliss-audio" -version = "0.2.6" +version = "0.3.0" dependencies = [ "bliss-audio-aubio-rs", "crossbeam", diff --git a/Cargo.toml b/Cargo.toml index 0f10b79..bd954ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bliss-audio" -version = "0.2.6" +version = "0.3.0" authors = ["Polochon-street "] edition = "2018" license = "GPL-3.0-only" @@ -19,9 +19,12 @@ no-default-features = true # https://github.com/zmwangx/rust-ffmpeg/pull/60 # or https://github.com/zmwangx/rust-ffmpeg/pull/62 is in default = ["bliss-audio-aubio-rs/static", "build-ffmpeg"] +# Build ffmpeg instead of using the host's. build-ffmpeg = ["ffmpeg-next/build"] -bench = [] +# Use if you want to build python bindings with maturin. python-bindings = ["bliss-audio-aubio-rs/fftw3"] +# Enable the benchmarks with `cargo +nightly bench --features=bench` +bench = [] [dependencies] ripemd160 = "0.9.0" diff --git a/README.md b/README.md index 585873e..c1929b0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ library used to make playlists by analyzing songs, and computing distance betwee Like bliss, it eases the creation of « intelligent » playlists and/or continuous play, à la Spotify/Grooveshark Radio, as well as easing creating plug-ins for -existing audio players. +existing audio players. For instance, you can use it to make calm playlists +to help you sleeping, fast playlists to get you started during the day, etc. For now (and if you're looking for an easy-to use smooth play experience), [blissify](https://crates.io/crates/blissify) implements bliss for diff --git a/data/no_tags.flac b/data/no_tags.flac new file mode 100644 index 0000000..59bdda6 Binary files /dev/null and b/data/no_tags.flac differ diff --git a/examples/distance.rs b/examples/distance.rs index 21606a2..3d0489c 100644 --- a/examples/distance.rs +++ b/examples/distance.rs @@ -17,7 +17,7 @@ fn main() -> Result<(), String> { let song2 = Song::new(&second_path).map_err(|x| x.to_string())?; println!( - "d({}, {}) = {}", + "d({:?}, {:?}) = {}", song1.path, song2.path, song1.distance(&song2) diff --git a/src/chroma.rs b/src/chroma.rs index 5c7bfb8..9a57dd9 100644 --- a/src/chroma.rs +++ b/src/chroma.rs @@ -7,7 +7,7 @@ extern crate noisy_float; use crate::utils::stft; use crate::utils::{hz_to_octs_inplace, Normalize}; -use crate::BlissError; +use crate::{BlissError, BlissResult}; use ndarray::{arr1, arr2, concatenate, s, Array, Array1, Array2, Axis, Zip}; use ndarray_stats::interpolate::Midpoint; use ndarray_stats::QuantileExt; @@ -51,7 +51,7 @@ impl ChromaDesc { * Passing a full song here once instead of streaming smaller parts of the * song will greatly improve accuracy. */ - pub fn do_(&mut self, signal: &[f32]) -> Result<(), BlissError> { + pub fn do_(&mut self, signal: &[f32]) -> BlissResult<()> { let mut stft = stft(signal, ChromaDesc::WINDOW_SIZE, 2205); let tuning = estimate_tuning( self.sample_rate as u32, @@ -155,7 +155,7 @@ fn chroma_filter( n_fft: usize, n_chroma: u32, tuning: f64, -) -> Result, BlissError> { +) -> BlissResult> { let ctroct = 5.0; let octwidth = 2.; let n_chroma_float = f64::from(n_chroma); @@ -226,7 +226,7 @@ fn pip_track( sample_rate: u32, spectrum: &Array2, n_fft: usize, -) -> Result<(Vec, Vec), BlissError> { +) -> BlissResult<(Vec, Vec)> { let sample_rate_float = f64::from(sample_rate); let fmin = 150.0_f64; let fmax = 4000.0_f64.min(sample_rate_float / 2.0); @@ -291,7 +291,7 @@ fn pitch_tuning( frequencies: &mut Array1, resolution: f64, bins_per_octave: u32, -) -> Result { +) -> BlissResult { if frequencies.is_empty() { return Ok(0.0); } @@ -320,7 +320,7 @@ fn estimate_tuning( n_fft: usize, resolution: f64, bins_per_octave: u32, -) -> Result { +) -> BlissResult { let (pitch, mag) = pip_track(sample_rate, &spectrum, n_fft)?; let (filtered_pitch, filtered_mag): (Vec, Vec) = pitch @@ -372,6 +372,7 @@ mod test { use ndarray::{arr1, arr2, Array2}; use ndarray_npy::ReadNpyExt; use std::fs::File; + use std::path::Path; #[test] fn test_chroma_interval_features() { @@ -437,7 +438,7 @@ mod test { #[test] fn test_chroma_desc() { - let song = Song::decode("data/s16_mono_22_5kHz.flac").unwrap(); + let song = Song::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![ @@ -459,7 +460,7 @@ mod test { #[test] fn test_chroma_stft_decode() { - let signal = Song::decode("data/s16_mono_22_5kHz.flac") + let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")) .unwrap() .sample_array; let mut stft = stft(&signal, 8192, 2205); @@ -487,7 +488,7 @@ mod test { #[test] fn test_estimate_tuning_decode() { - let signal = Song::decode("data/s16_mono_22_5kHz.flac") + let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")) .unwrap() .sample_array; let stft = stft(&signal, 8192, 2205); @@ -556,6 +557,7 @@ mod bench { use ndarray_npy::ReadNpyExt; use std::fs::File; use test::Bencher; + use std::path::Path; #[bench] fn bench_estimate_tuning(b: &mut Bencher) { @@ -603,7 +605,7 @@ mod bench { #[bench] fn bench_chroma_desc(b: &mut Bencher) { - let song = Song::decode("data/s16_mono_22_5kHz.flac").unwrap(); + let song = Song::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(|| { @@ -614,7 +616,7 @@ mod bench { #[bench] fn bench_chroma_stft(b: &mut Bencher) { - let song = Song::decode("data/s16_mono_22_5kHz.flac").unwrap(); + let song = Song::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(|| { @@ -625,7 +627,7 @@ mod bench { #[bench] fn bench_chroma_stft_decode(b: &mut Bencher) { - let signal = Song::decode("data/s16_mono_22_5kHz.flac") + let signal = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")) .unwrap() .sample_array; let mut stft = stft(&signal, 8192, 2205); diff --git a/src/lib.rs b/src/lib.rs index d415017..b96d81e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,9 +24,9 @@ //! //! ## Analyze & compute the distance between two songs //! ```no_run -//! use bliss_audio::{BlissError, Song}; +//! use bliss_audio::{BlissResult, Song}; //! -//! fn main() -> Result<(), BlissError> { +//! fn main() -> BlissResult<()> { //! let song1 = Song::new("/path/to/song1")?; //! let song2 = Song::new("/path/to/song2")?; //! @@ -37,15 +37,15 @@ //! //! ### Make a playlist from a song //! ```no_run -//! use bliss_audio::{BlissError, Song}; +//! use bliss_audio::{BlissResult, Song}; //! use noisy_float::prelude::n32; //! -//! fn main() -> Result<(), BlissError> { +//! fn main() -> BlissResult<()> { //! let paths = vec!["/path/to/song1", "/path/to/song2", "/path/to/song3"]; //! let mut songs: Vec = paths //! .iter() //! .map(|path| Song::new(path)) -//! .collect::, BlissError>>()?; +//! .collect::>>()?; //! //! // Assuming there is a first song //! let first_song = songs.first().unwrap().to_owned(); @@ -55,8 +55,8 @@ //! "Playlist is: {:?}", //! songs //! .iter() -//! .map(|song| &song.path) -//! .collect::>() +//! .map(|song| song.path.to_string_lossy().to_string()) +//! .collect::>() //! ); //! Ok(()) //! } @@ -100,13 +100,16 @@ pub enum BlissError { ProviderError(String), } +/// bliss error type +pub type BlissResult = Result; + /// Simple function to bulk analyze a set of songs represented by their /// absolute paths. /// /// When making an extension for an audio player, prefer /// implementing the `Library` trait. #[doc(hidden)] -pub fn bulk_analyse(paths: Vec) -> Vec> { +pub fn bulk_analyse(paths: Vec) -> Vec> { let mut songs = Vec::with_capacity(paths.len()); let num_cpus = num_cpus::get(); @@ -175,7 +178,7 @@ mod tests { let mut analysed_songs: Vec = results .iter() - .filter_map(|x| x.as_ref().ok().map(|x| x.path.to_string())) + .filter_map(|x| x.as_ref().ok().map(|x| x.path.to_str().unwrap().to_string())) .collect(); analysed_songs.sort_by(|a, b| a.cmp(b)); diff --git a/src/library.rs b/src/library.rs index 2d39978..aeddc30 100644 --- a/src/library.rs +++ b/src/library.rs @@ -1,6 +1,6 @@ //! Module containing the Library trait, useful to get started to implement //! a plug-in for an audio player. -use crate::{BlissError, Song}; +use crate::{BlissError, BlissResult, Song}; use log::{debug, error, info}; use noisy_float::prelude::*; use std::sync::mpsc; @@ -11,18 +11,18 @@ use std::thread; pub trait Library { /// Return the absolute path of all the songs in an /// audio player's music library. - fn get_songs_paths(&self) -> Result, BlissError>; + fn get_songs_paths(&self) -> BlissResult>; /// Store an analyzed Song object in some (cold) storage, e.g. /// a database, a file... - fn store_song(&mut self, song: &Song) -> Result<(), BlissError>; + fn store_song(&mut self, song: &Song) -> BlissResult<()>; /// Log and / or store that an error happened while trying to decode and /// analyze a song. - fn store_error_song(&mut self, song_path: String, error: BlissError) -> Result<(), BlissError>; + fn store_error_song(&mut self, song_path: String, error: BlissError) -> BlissResult<()>; /// Retrieve a list of all the stored Songs. /// /// This should work only after having run `analyze_library` at least /// once. - fn get_stored_songs(&self) -> Result, BlissError>; + fn get_stored_songs(&self) -> BlissResult>; /// Return a list of songs that are similar to ``first_song``. /// @@ -41,7 +41,7 @@ pub trait Library { &self, first_song: Song, playlist_length: usize, - ) -> Result, BlissError> { + ) -> BlissResult> { let analysis_current_song = first_song.analysis; let mut songs = self.get_stored_songs()?; songs.sort_by_cached_key(|song| n32(analysis_current_song.distance(&song.analysis))); @@ -59,7 +59,7 @@ pub trait Library { /// /// Note: this is mostly useful for updating a song library. For the first /// run, you probably want to use `analyze_library`. - fn analyze_paths(&mut self, paths: Vec) -> Result<(), BlissError> { + fn analyze_paths(&mut self, paths: Vec) -> BlissResult<()> { if paths.is_empty() { return Ok(()); } @@ -67,8 +67,8 @@ pub trait Library { #[allow(clippy::type_complexity)] let (tx, rx): ( - Sender<(String, Result)>, - Receiver<(String, Result)>, + Sender<(String, BlissResult)>, + Receiver<(String, BlissResult)>, ) = mpsc::channel(); let mut handles = Vec::new(); let mut chunk_length = paths.len() / num_cpus; @@ -96,8 +96,8 @@ pub trait Library { match song { Ok(song) => { self.store_song(&song) - .unwrap_or_else(|_| error!("Error while storing song '{}'", song.path)); - info!("Analyzed and stored song '{}' successfully.", song.path) + .unwrap_or_else(|_| error!("Error while storing song '{}'", song.path.display())); + info!("Analyzed and stored song '{}' successfully.", song.path.display()) } Err(e) => { self.store_error_song(path.to_string(), e.to_owned()) @@ -122,7 +122,7 @@ pub trait Library { /// Analyzes a song library, using `get_songs_paths`, `store_song` and /// `store_error_song` implementations. - fn analyze_library(&mut self) -> Result<(), BlissError> { + fn analyze_library(&mut self) -> BlissResult<()> { let paths = self .get_songs_paths() .map_err(|e| BlissError::ProviderError(e.to_string()))?; @@ -135,6 +135,7 @@ pub trait Library { mod test { use super::*; use crate::song::Analysis; + use std::path::Path; #[derive(Default)] struct TestLibrary { @@ -143,7 +144,7 @@ mod test { } impl Library for TestLibrary { - fn get_songs_paths(&self) -> Result, BlissError> { + fn get_songs_paths(&self) -> BlissResult> { Ok(vec![ String::from("./data/white_noise.flac"), String::from("./data/s16_mono_22_5kHz.flac"), @@ -152,7 +153,7 @@ mod test { ]) } - fn store_song(&mut self, song: &Song) -> Result<(), BlissError> { + fn store_song(&mut self, song: &Song) -> BlissResult<()> { self.internal_storage.push(song.to_owned()); Ok(()) } @@ -161,12 +162,12 @@ mod test { &mut self, song_path: String, error: BlissError, - ) -> Result<(), BlissError> { + ) -> BlissResult<()> { self.failed_files.push((song_path, error.to_string())); Ok(()) } - fn get_stored_songs(&self) -> Result, BlissError> { + fn get_stored_songs(&self) -> BlissResult> { Ok(self.internal_storage.to_owned()) } } @@ -175,23 +176,23 @@ mod test { struct FailingLibrary; impl Library for FailingLibrary { - fn get_songs_paths(&self) -> Result, BlissError> { + fn get_songs_paths(&self) -> BlissResult> { Err(BlissError::ProviderError(String::from( "Could not get songs path", ))) } - fn store_song(&mut self, _: &Song) -> Result<(), BlissError> { + fn store_song(&mut self, _: &Song) -> BlissResult<()> { Ok(()) } - fn get_stored_songs(&self) -> Result, BlissError> { + fn get_stored_songs(&self) -> BlissResult> { Err(BlissError::ProviderError(String::from( "Could not get stored songs", ))) } - fn store_error_song(&mut self, _: String, _: BlissError) -> Result<(), BlissError> { + fn store_error_song(&mut self, _: String, _: BlissError) -> BlissResult<()> { Ok(()) } } @@ -200,7 +201,7 @@ mod test { struct FailingStorage; impl Library for FailingStorage { - fn get_songs_paths(&self) -> Result, BlissError> { + fn get_songs_paths(&self) -> BlissResult> { Ok(vec![ String::from("./data/white_noise.flac"), String::from("./data/s16_mono_22_5kHz.flac"), @@ -209,14 +210,14 @@ mod test { ]) } - fn store_song(&mut self, song: &Song) -> Result<(), BlissError> { + fn store_song(&mut self, song: &Song) -> BlissResult<()> { Err(BlissError::ProviderError(format!( "Could not store song {}", - song.path + song.path.display() ))) } - fn get_stored_songs(&self) -> Result, BlissError> { + fn get_stored_songs(&self) -> BlissResult> { Ok(vec![]) } @@ -224,7 +225,7 @@ mod test { &mut self, song_path: String, error: BlissError, - ) -> Result<(), BlissError> { + ) -> BlissResult<()> { Err(BlissError::ProviderError(format!( "Could not store errored song: {}, with error: {}", song_path, error @@ -247,7 +248,7 @@ mod test { fn test_playlist_from_song_fail() { let test_library = FailingLibrary {}; let song = Song { - path: String::from("path-to-first"), + path: Path::new("path-to-first").to_path_buf(), analysis: Analysis::new([0.; 20]), ..Default::default() }; @@ -294,7 +295,7 @@ mod test { let mut songs = test_library .internal_storage .iter() - .map(|x| x.path.to_owned()) + .map(|x| x.path.to_str().unwrap().to_string()) .collect::>(); songs.sort(); @@ -311,25 +312,25 @@ mod test { fn test_playlist_from_song() { let mut test_library = TestLibrary::default(); let first_song = Song { - path: String::from("path-to-first"), + path: Path::new("path-to-first").to_path_buf(), analysis: Analysis::new([0.; 20]), ..Default::default() }; let second_song = Song { - path: String::from("path-to-second"), + path: Path::new("path-to-second").to_path_buf(), analysis: Analysis::new([0.1; 20]), ..Default::default() }; let third_song = Song { - path: String::from("path-to-third"), + path: Path::new("path-to-third").to_path_buf(), analysis: Analysis::new([10.; 20]), ..Default::default() }; let fourth_song = Song { - path: String::from("path-to-fourth"), + path: Path::new("path-to-fourth").to_path_buf(), analysis: Analysis::new([20.; 20]), ..Default::default() }; @@ -350,19 +351,19 @@ mod test { fn test_playlist_from_song_too_little_songs() { let mut test_library = TestLibrary::default(); let first_song = Song { - path: String::from("path-to-first"), + path: Path::new("path-to-first").to_path_buf(), analysis: Analysis::new([0.; 20]), ..Default::default() }; let second_song = Song { - path: String::from("path-to-second"), + path: Path::new("path-to-second").to_path_buf(), analysis: Analysis::new([0.1; 20]), ..Default::default() }; let third_song = Song { - path: String::from("path-to-third"), + path: Path::new("path-to-third").to_path_buf(), analysis: Analysis::new([10.; 20]), ..Default::default() }; diff --git a/src/misc.rs b/src/misc.rs index 63dc2d1..1693590 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -64,10 +64,11 @@ impl Normalize for LoudnessDesc { mod tests { use super::*; use crate::Song; + use std::path::Path; #[test] fn test_loudness() { - let song = Song::decode("data/s16_mono_22_5kHz.flac").unwrap(); + let song = Song::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap(); let mut loudness_desc = LoudnessDesc::default(); for chunk in song.sample_array.chunks_exact(LoudnessDesc::WINDOW_SIZE) { loudness_desc.do_(&chunk); diff --git a/src/song.rs b/src/song.rs index f7587f3..5340c38 100644 --- a/src/song.rs +++ b/src/song.rs @@ -17,7 +17,7 @@ use crate::chroma::ChromaDesc; use crate::misc::LoudnessDesc; use crate::temporal::BPMDesc; use crate::timbral::{SpectralDesc, ZeroCrossingRateDesc}; -use crate::{BlissError, SAMPLE_RATE}; +use crate::{BlissError, BlissResult, SAMPLE_RATE}; use ::log::warn; use core::ops::Index; use crossbeam::thread; @@ -36,6 +36,8 @@ use std::convert::TryInto; use std::fmt; use std::sync::mpsc; use std::sync::mpsc::Receiver; +use std::path::Path; +use std::path::PathBuf; use std::thread as std_thread; use strum::{EnumCount, IntoEnumIterator}; use strum_macros::{EnumCount, EnumIter}; @@ -46,17 +48,17 @@ use strum_macros::{EnumCount, EnumIter}; /// other metadata (artist, genre...) pub struct Song { /// Song's provided file path - pub path: String, - /// Song's artist, read from the metadata (`""` if empty) - pub artist: String, - /// Song's title, read from the metadata (`""` if empty) - pub title: String, - /// Song's album name, read from the metadata (`""` if empty) - pub album: String, - /// Song's tracked number, read from the metadata (`""` if empty) - pub track_number: String, + pub path: PathBuf, + /// Song's artist, read from the metadata + pub artist: Option, + /// Song's title, read from the metadata + pub title: Option, + /// Song's album name, read from the metadata + pub album: Option, + /// Song's tracked number, read from the metadata + pub track_number: Option, /// Song's genre, read from the metadata (`""` if empty) - pub genre: String, + pub genre: Option, /// bliss analysis results pub analysis: Analysis, } @@ -66,9 +68,9 @@ pub struct Song { /// /// * Example: /// ```no_run -/// use bliss_audio::{AnalysisIndex, BlissError, Song}; +/// use bliss_audio::{AnalysisIndex, BlissResult, Song}; /// -/// fn main() -> Result<(), BlissError> { +/// fn main() -> BlissResult<()> { /// let song = Song::new("path/to/song")?; /// println!("{}", song.analysis[AnalysisIndex::Tempo]); /// Ok(()) @@ -217,8 +219,8 @@ 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. - pub fn new(path: &str) -> Result { - let raw_song = Song::decode(&path)?; + pub fn new>(path: P) -> BlissResult { + let raw_song = Song::decode(path.as_ref())?; Ok(Song { path: raw_song.path, @@ -242,7 +244,7 @@ impl Song { * Useful in the rare cases where the full song is not * completely available. **/ - fn analyse(sample_array: Vec) -> Result { + fn analyse(sample_array: Vec) -> BlissResult { let largest_window = vec![ BPMDesc::WINDOW_SIZE, ChromaDesc::WINDOW_SIZE, @@ -259,7 +261,7 @@ impl Song { } thread::scope(|s| { - let child_tempo: thread::ScopedJoinHandle<'_, Result> = + let child_tempo: thread::ScopedJoinHandle<'_, BlissResult> = s.spawn(|_| { let mut tempo_desc = BPMDesc::new(SAMPLE_RATE)?; let windows = sample_array @@ -272,7 +274,7 @@ impl Song { Ok(tempo_desc.get_value()) }); - let child_chroma: thread::ScopedJoinHandle<'_, Result, BlissError>> = + let child_chroma: thread::ScopedJoinHandle<'_, BlissResult>> = s.spawn(|_| { let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12); chroma_desc.do_(&sample_array)?; @@ -282,7 +284,7 @@ impl Song { #[allow(clippy::type_complexity)] let child_timbral: thread::ScopedJoinHandle< '_, - Result<(Vec, Vec, Vec), BlissError>, + BlissResult<(Vec, Vec, Vec)>, > = s.spawn(|_| { let mut spectral_desc = SpectralDesc::new(SAMPLE_RATE)?; let windows = sample_array @@ -297,13 +299,13 @@ impl Song { Ok((centroid, rolloff, flatness)) }); - let child_zcr: thread::ScopedJoinHandle<'_, Result> = s.spawn(|_| { + let child_zcr: thread::ScopedJoinHandle<'_, BlissResult> = s.spawn(|_| { let mut zcr_desc = ZeroCrossingRateDesc::default(); zcr_desc.do_(&sample_array); Ok(zcr_desc.get_value()) }); - let child_loudness: thread::ScopedJoinHandle<'_, Result, BlissError>> = s + let child_loudness: thread::ScopedJoinHandle<'_, BlissResult>> = s .spawn(|_| { let mut loudness_desc = LoudnessDesc::default(); let windows = sample_array.chunks(LoudnessDesc::WINDOW_SIZE); @@ -339,12 +341,12 @@ impl Song { .unwrap() } - pub(crate) fn decode(path: &str) -> Result { + pub(crate) fn decode(path: &Path) -> BlissResult { ffmpeg::init() .map_err(|e| BlissError::DecodingError(format!("ffmpeg init error: {:?}.", e)))?; log::set_level(Level::Quiet); let mut song = InternalSong { - path: path.to_string(), + path: path.into(), ..Default::default() }; let mut format = ffmpeg::format::input(&path) @@ -378,19 +380,35 @@ impl Song { }; let sample_array: Vec = Vec::with_capacity(expected_sample_number as usize); if let Some(title) = format.metadata().get("title") { - song.title = title.to_string(); + song.title = match title { + "" => None, + t => Some(t.to_string()), + }; }; if let Some(artist) = format.metadata().get("artist") { - song.artist = artist.to_string(); + song.artist = match artist { + "" => None, + a => Some(a.to_string()), + }; + }; if let Some(album) = format.metadata().get("album") { - song.album = album.to_string(); + song.album = match album { + "" => None, + a => Some(a.to_string()), + }; }; if let Some(genre) = format.metadata().get("genre") { - song.genre = genre.to_string(); + song.genre = match genre { + "" => None, + g => Some(g.to_string()), + }; }; if let Some(track_number) = format.metadata().get("track") { - song.track_number = track_number.to_string(); + song.track_number = match track_number { + "" => None, + t => Some(t.to_string()), + }; }; let in_channel_layout = { if codec.channel_layout() == ChannelLayout::empty() { @@ -494,12 +512,12 @@ 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 path: PathBuf, + pub artist: Option, + pub title: Option, + pub album: Option, + pub track_number: Option, + pub genre: Option, pub sample_array: Vec, } @@ -507,7 +525,7 @@ fn resample_frame( rx: Receiver