A song analysis library for making playlists

Overview

crate build doc

bliss music analyser - Rust version

bliss-rs is the Rust improvement of bliss, a library used to make playlists by analyzing songs, and computing distance between them.

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. 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 implements bliss for MPD.

There are also python bindings.

Note 1: the features bliss-rs outputs is not compatible with the ones used by C-bliss, since it uses different, more accurate values, based on actual literature. It is also faster.

Examples

For simple analysis / distance computing, a look at examples/distance.rs and examples/analyse.rs.

Ready to use examples:

Compute the distance between two songs

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

= paths .iter() .map(|path| Song::new(path)) .collect::, 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::>() ); Ok(()) } ">
use bliss_audio::{BlissError, Song};
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 = paths
        .iter()
        .map(|path| Song::new(path))
        .collect::, 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::>()
    );
    Ok(())
}

Further use

Instead of reinventing ways to fetch a user library, play songs, etc, and embed that into bliss, it is easier to look at the Library trait.

By implementing a few functions to get songs from a media library, and store the resulting analysis, you get access to functions to analyze an entire library (with multithreading), and to make playlists easily.

See blissify for a reference implementation.

Acknowledgements

  • This library relies heavily on aubio's Rust bindings for the spectral / timbral analysis, so a big thanks to both the creators and contributors of librosa, and to @katyo for making aubio bindings for Rust.
  • The first part of the chroma extraction is basically a rewrite of librosa's chroma feature extraction from python to Rust, with just as little features as needed. Thanks to both creators and contributors as well.
  • Finally, a big thanks to Christof Weiss for pointing me in the right direction for the chroma feature summarization, which are basically also a rewrite from Python to Rust of some of the awesome notebooks by AudioLabs Erlangen, that you can find here.
Comments
  • [Feature] Analyse segments of files (to aid CUE support)

    [Feature] Analyse segments of files (to aid CUE support)

    I'm using bliss-rs analysis to provide a mix of similar tracks for LMS. LMS, as well as MPD, support reading tracks from CUE files, but bliss-rs does not. In my analysis code (https://github.com/CDrummond/bliss-analyser/blob/master/src/analyse.rs) I use ffmpeg (via calling ffmpeg executable) to create temporary files for each track within a CUE's main file. It would be great, however, if bliss-rs supported this itself.

    The simplest way would be somehing like (forgive the syntax, I'm very new to Rust):

    pub fn Song::from_segment(path: Path, start: Duration, length: Duration) -> BlissResult<Self> {
      ...
    }
    
    opened by CDrummond 29
  • Treat ffmpeg decode failures as errors

    Treat ffmpeg decode failures as errors

    When processing my music library (with blissify) I ran into several segfaults from ffmpeg.

    It seems these tracks (which are probably corrupt or something...) failed to decode here, but since that's only treated as a warning, not an error, the program continued on and ffmpeg segfaulted while trying to resample them.

    I can probably find an innocuous song for you to reproduce with if you want.

    A patch like:

    diff --git a/src/song.rs b/src/song.rs
    index 374125b9a51a..35461e0278d9 100644
    --- a/src/song.rs
    +++ b/src/song.rs
    @@ -563,7 +563,12 @@ impl Song {
                         song.sample_array = child.join().unwrap()?;
                         return Ok(song);
                     }
    -                Err(e) => warn!("error while decoding file '{}': {}", path.display(), e),
    +                Err(e) => {
    +                    return Err(BlissError::DecodingError(format!(
    +                        "error decoding file: {}.",
    +                        e,
    +                    )))
    +                }
                 };
                 
                 loop {
    

    made it get past those songs for me.

    Here is the stack trace:

    #0  __memmove_avx_unaligned_erms ()
        at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:451
    #1  0x00007fcd1d963c98 in  () at /lib/x86_64-linux-gnu/libswresample.so.4
    #2  0x00007fcd1d964709 in  () at /lib/x86_64-linux-gnu/libswresample.so.4
    #3  0x00007fcd1d964f15 in  () at /lib/x86_64-linux-gnu/libswresample.so.4
    #4  0x00007fcd1d965a15 in swr_convert ()
        at /lib/x86_64-linux-gnu/libswresample.so.4
    #5  0x00007fcd1d96689e in swr_convert_frame ()
        at /lib/x86_64-linux-gnu/libswresample.so.4
    #6  0x00005651e1301bbf in ffmpeg_next::software::resampling::context::Context::run (self=0x7fcc8f9eb4c0, input=0x7fcc8f9eb620, output=0x7fcc8f9eb5e8)
        at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/ffmpeg-next-5.1.1/src/software/resampling/context.rs:158
    #7  0x00005651e09d5ffb in bliss_audio::song::resample_frame
        (rx=..., in_codec_format=..., in_channel_layout=..., in_rate=44100, sample_array=...)
        at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/bliss-audio-0.5.2/src/song.rs:669
    #8  0x00005651e09d568f in bliss_audio::song::{impl#3}::decode::{closure#6} ()
        at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/bliss-audio-0.5.2/src/song.rs:537
    #9  0x00005651e0baa67b in std::sys_common::backtrace::__rust_begin_short_backtrace<bliss_audio::song::{impl#3}::decode::{closure#6}, core::result::Result<alloc:--Type <RET> for more, q to quit, c to continue without paging--c
    :vec::Vec<f32, alloc::alloc::Global>, bliss_audio::BlissError>> (f=...) at /usr/src/rustc-1.56.0/library/std/src/sys_common/backtrace.rs:125
    #10 0x00005651e09e368c in std::thread::{impl#0}::spawn_unchecked::{closure#0}::{closure#0}<bliss_audio::song::{impl#3}::decode::{closure#6}, core::result::Result<alloc::vec::Vec<f32, alloc::alloc::Global>, bliss_audio::BlissError>> () at /usr/src/rustc-1.56.0/library/std/src/thread/mod.rs:481
    #11 0x00005651e086dbfd in core::panic::unwind_safe::{impl#23}::call_once<core::result::Result<alloc::vec::Vec<f32, alloc::alloc::Global>, bliss_audio::BlissError>, std::thread::{impl#0}::spawn_unchecked::{closure#0}::{closure#0}> (self=..., _args=()) at /usr/src/rustc-1.56.0/library/core/src/panic/unwind_safe.rs:271
    #12 0x00005651e0870cfb in std::panicking::try::do_call<core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked::{closure#0}::{closure#0}>, core::result::Result<alloc::vec::Vec<f32, alloc::alloc::Global>, bliss_audio::BlissError>> (data=0x7fcc8f9ebba8) at /usr/src/rustc-1.56.0/library/std/src/panicking.rs:403
    #13 0x00005651e088758b in __rust_try ()
    #14 0x00005651e0870768 in std::panicking::try<core::result::Result<alloc::vec::Vec<f32, alloc::alloc::Global>, bliss_audio::BlissError>, core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked::{closure#0}::{closure#0}>> (f=...) at /usr/src/rustc-1.56.0/library/std/src/panicking.rs:367
    #15 0x00005651e0ad84fc in std::panic::catch_unwind<core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked::{closure#0}::{closure#0}>, core::result::Result<alloc::vec::Vec<f32, alloc::alloc::Global>, bliss_audio::BlissError>> (f=...) at /usr/src/rustc-1.56.0/library/std/src/panic.rs:129
    #16 0x00005651e09e23d4 in std::thread::{impl#0}::spawn_unchecked::{closure#0}<bliss_audio::song::{impl#3}::decode::{closure#6}, core::result::Result<alloc::vec::Vec<f32, alloc::alloc::Global>, bliss_audio::BlissError>> () at /usr/src/rustc-1.56.0/library/std/src/thread/mod.rs:480
    #17 0x00005651e089590f in core::ops::function::FnOnce::call_once<std::thread::{impl#0}::spawn_unchecked::{closure#0}, ()> () at /usr/src/rustc-1.56.0/library/core/src/ops/function.rs:227
    #18 0x00005651e133ee63 in alloc::boxed::{impl#44}::call_once<(), dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global> (self=..., args=()) at /usr/src/rustc-1.56.0/library/alloc/src/boxed.rs:1636
    #19 alloc::boxed::{impl#44}::call_once<(), alloc::boxed::Box<dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global>, alloc::alloc::Global> (self=0x7fcca00a77e0, args=()) at /usr/src/rustc-1.56.0/library/alloc/src/boxed.rs:1636
    #20 std::sys::unix::thread::{impl#2}::new::thread_start (main=0x7fcca00a77e0) at library/std/src/sys/unix/thread.rs:106
    #21 0x00007fcd1d91ad80 in start_thread (arg=0x7fcc8f9fc640) at pthread_create.c:481
    #22 0x00007fcd1b32176f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:9
    
    
    opened by toofar 8
  • Use set for paths-to-update comparison

    Use set for paths-to-update comparison

    With about 140k tracks the update operation takes a long time. A flamegraph shows update_library_convert_extra_info as taking almost all of that time and slice_contains in particular.

    With the previous release 0.2.9 updates where very fast and got to the actual analyzing part right away. With 0.3.2 it spends a lot of time before it even gets to analyzing. And it seems to be slower to start up the more songs you have analyzed.

    Blissify 0.2.9 seemed to use a HashSet too :)

    image

    opened by toofar 3
  • [Feature] Add 'AlbumArtist' to Song struct

    [Feature] Add 'AlbumArtist' to Song struct

    To allow preventing using tracks from same album for a specified number of tracks, please add 'Album Artist' to the metadata stored in the Song struct.

    opened by CDrummond 1
  • Error handling for tags: Null and encoding

    Error handling for tags: Null and encoding

    I've reprocessed my library with the new blissify version and am now running into a couple of issues around tags when trying to generate a playlist.

    1. Null tag values

    I have some music files without tags, and even some that have valid flags that don't end up in the database. Those are causing panics where the DB rows are being serialized into Song instances: https://github.com/Polochon-street/bliss-rs/blob/8391095/src/library.rs#L1045

    The error is Invalid column type Null at index: 2, name: title.. Some files just don't have tags which you can probably reproduce easily enough. There is one interesting example I saw which is a flac file with valid Id3 tags and an empty vorbis comment block. Weird but it works fine with MPD! Here it is (temp upload again): https://litter.catbox.moe/5k2sck.flac

    Most of the files it is erroring on just don't have tags though. I could add tags easy enough but these files are working with all my other tooling. I'm using blissify to play around with the feature analysis features and I feel like that could conceivably work without good tags.

    1. invalid utf-8 sequence

    There are a few files where rusqlite is claiming tags have invalid utf-8 sequences when it tries to convert them to String (same piece of code as above). I'm not sure what's going on here, Id3 tags can contain non-utf8 text (I think only 2.4 added a Unicode type?) but these values where added to the DB by blissify fine and they look fine in log messages and other tools. Here's an example (it's the title tag I think): https://litter.catbox.moe/87dlzk.mp3

    opened by toofar 2
  • bliss-audio 0.6.3 not available?

    bliss-audio 0.6.3 not available?

    Hi,

    I am usually building bliss-analyser (https://github.com/CDrummond/bliss-analyser) on a piCorePlayer (Raspberry Pi 4B). A couple of days ago bliss-analyser switched to bliss-audio 0.6.3 and now building bliss-analyser fails with the following error

        Updating crates.io index
    error: failed to select a version for the requirement `bliss-audio = "^0.6.3"`
    candidate versions found which didn't match: 0.6.1, 0.6.0, 0.5.3, ...
    location searched: crates.io index
    required by package `bliss-analyser v0.2.2 (/mnt/mmcblk0p2/bliss-analyzer/src)`
    

    Deleting and re-populating the cargo registry did not help. And anyway, when checking the cargo registry I do find entries for 0.6.3 and 0.6.4 in registry/index/github.com-1ecc6299db9ec823/.cache/bl/is/bliss-audio

    Not sure if this is a problem in bliss-rs, bliss-analyser, or my build environment... does anybody have some hints? Please?

    Best regards, Christoph

    opened by chrober 3
A command line application to organize Spotify playlists.

Sortify A command line application to organize Spotify playlists. Using Sortify, you can choose a playlist as the source playlist, and then have the o

Francisco Cunha 4 Aug 14, 2024
A multi-threaded GTK 3 application for fetching the lyrics of the current playing song

Lyriek A multi-threaded GTK 3 application for fetching the lyrics of the current playing song. Installation Arch Linux yay -S lyriek Ubuntu apt insta

Bart Willems 18 Feb 9, 2022
a fast song selector for mpd

rinse a fast song selector for mpd The way I typically use mpd is to add all of my songs to a single playlist with random enabled and use mpc with med

David Sherriff 17 Nov 29, 2022
A low-level windowing system geared towards making audio plugin UIs.

baseview A low-level windowing system geared towards making audio plugin UIs. baseview abstracts the platform-specific windowing APIs (winapi, cocoa,

null 155 Dec 30, 2022
A rust binding for the FMOD library

rust-fmod This is a rust binding for FMOD, the library developped by FIRELIGHT TECHNOLOGIES. FMOD website : www.fmod.org You can also find it on crate

Guillaume Gomez 55 Nov 2, 2022
Cross-platform audio I/O library in pure Rust

CPAL - Cross-Platform Audio Library Low-level library for audio input and output in pure Rust. This library currently supports the following: Enumerat

null 1.8k Jan 8, 2023
Rust audio playback library

Audio playback library Rust playback library. Playback is handled by cpal. MP3 decoding is handled by minimp3. WAV decoding is handled by hound. Vorbi

null 1.2k Jan 1, 2023
Rust bindings for the soloud audio engine library

soloud-rs A crossplatform Rust bindings for the soloud audio engine library. Supported formats: wav, mp3, ogg, flac. The library also comes with a spe

Mohammed Alyousef 38 Dec 8, 2022
Symphonia is a pure Rust audio decoding and media demuxing library supporting AAC, FLAC, MP3, MP4, OGG, Vorbis, and WAV.

Pure Rust multimedia format demuxing, tag reading, and audio decoding library

Philip Deljanov 1k Jan 2, 2023
Rust Sound Synthesis Library

Oscen Oscen [“oh-sin”] is a library for building modular synthesizers in Rust. It contains a collection of components frequently used in sound synthes

Reed Rosenbluth 104 Nov 2, 2022
A wav encoding and decoding library in Rust

Hound A wav encoding and decoding library in Rust. Hound can read and write the WAVE audio format, an ubiquitous format for raw, uncompressed audio. T

Ruud van Asseldonk 345 Dec 27, 2022
Small music theory library with MIDI capabilities written in Rust

mumuse Small music theory library with MIDI capabilities written in Rust (wip). Examples Creating notes and transpositions // Declare Note from &str l

Alexis LOUIS 4 Jul 27, 2022
ears is a simple library to play Sounds and Musics in Rust

ears ears is a simple library to play Sounds and Musics in Rust. ears is build on the top of OpenAL and libsndfile. Provides an access to the OpenAL s

Jeremy Letang 56 Dec 1, 2022
A library for constructing Groth-Sahai proofs using pre-built wrappers

Groth-Sahai Wrappers A Rust library containing wrappers that facilitate the construction of non-interactive witness-indistinguishable and zero-knowled

Jacob White 1 Mar 7, 2022
Flutter crossplatform audio playback library powered by golang beep & oto

Rowdy Pure Rust based Dart/Flutter audio playback library Installation $ flutter pub add rowdy Configuration You'll need the Rust toolchain installed

Kingkor Roy Tirtho 3 Aug 31, 2022
A library and application for lossless, format-preserving, two-pass optimization and repair of Vorbis data, reducing its size without altering any audio information.

OptiVorbis A library and application for lossless, format-preserving, two-pass optimization and repair of Vorbis data, reducing its size without alter

OptiVorbis 27 Jan 3, 2023
A CLI and library to convert data to sound, and vice versa (dependency-free)

Data to sound A simple crate to convert data to sound, and sound to data. The sound file format is wave (.wav). You can use it as a library or as a co

Awiteb 8 Feb 28, 2023
TinyAudio is a cross-platform audio output library

TinyAudio TinyAudio is a cross-platform audio output library. Its main goal to provide unified access to a default sound output device of your operati

Dmitry Stepanov 39 Apr 4, 2023
A low-overhead and adaptable audio playback library for Rust

Awedio   A low-overhead and adaptable audio playback library for Rust. Examples Play a single sound file: let (mut manager, backend) = awedio::start()

10 Buttons 20 May 25, 2023