# virittaa
[](https://builds.sr.ht/~mtmn/virittaa?)
Command-line BPM and key detection for audio files, backed by
[Essentia](https://github.com/MTG/essentia): tempo from `RhythmExtractor2013`
(multifeature), key from `KeyExtractor` (EDMA profile). The reported BPM is
Essentia's tempo estimate, not folded into a fixed octave range.
Detected values can be written back as tags and are stored in a local database
that can be queried by artist, key, BPM range, or regex. Mixxx libraries can be
imported from their SQLite database.
## Building
virittaa binds Essentia through the
[`essentia`](https://github.com/lagmoellertim/essentia-rs) crate (essentia-rs),
which links a system `libessentia` and its dependencies via `pkg-config`. These
must resolve:
`essentia`, `eigen3`, `yaml-0.1`, `fftw3f`, `taglib`, `samplerate`,
`libchromaprint`, `libavformat`, `libswresample`, `libavcodec`, `libavutil`
Essentia is rarely packaged as a library, so it generally has to be built from
source. Build it from a recent `master`, not the `v2.1_beta5` tag: beta5 needs
`libavresample`, removed in ffmpeg 5, while essentia-rs probes for
`libswresample`. Apply `nix/essentia-network-thread-local.patch` — without it,
analysis on more than one thread aborts with a double free.
A C++17 compiler is required; the bindings are generated and compiled with
`cxx`. essentia-rs links TensorFlow unless `USE_TENSORFLOW=0`, which
`.cargo/config.toml` sets.
essentia-rs is a git dependency (`ssh://anonymous@mtmn.name/essentia-rs.git`),
so the remote must be reachable over ssh:
```sh
cargo build --release
```
### Nix
```sh
nix build .#virittaa # or .#essentia for the library alone
nix flake check # fmt + clippy, a second full build of the tree
```
The flake builds libessentia from `nix/essentia.nix`, vendors essentia-rs from
its `essentia-rs` input, and repoints the manifest at the vendored copy, so the
sandbox needs no network. virittaa's own sources come from git — untracked files
are invisible to the build.
### Void Linux
`hack/void-build.rb`, as root, builds libessentia and virittaa in a Void chroot
and emits a relocatable bundle: the binary plus its private libraries under an
`$ORIGIN` rpath, runnable from anywhere without `LD_LIBRARY_PATH`, and installed
into the invoking user's `~/.local` unless `-n`. Options: `--help`.
## Usage
```sh
virittaa [OPTIONS] [PATH]...
```
`[PATH]...` — files or directories to analyze; omit when querying the database.
| Option | |
| --- | --- |
| `-t`, `--write-tags` | Write detected BPM and key to file metadata |
| `--no-store` | Do not save reports to the database |
| `-f`, `--force` | Re-analyze files that already carry BPM/key tags |
| `-j`, `--jobs <N>` | Thread count (0 = all cores) |
| `-l`, `--list` | List reports in the database |
| `--limit <N>` | Cap listed entries (default 1000, 0 = all) |
| `--query <KEY>` | Look up one track by exact key |
| `-S`, `--search <PATTERN>` | Search reports by regex |
| `-a`, `--artist <PREFIX>` | Match on artist prefix |
| `--key <PREFIX>` | Match on key prefix, e.g. `C Major`, `Am` |
| `--bpm <MIN> <MAX>` | Match on BPM range |
| `--import-mixxx <PATH>` | Import from a Mixxx SQLite database |
| `--db-path <PATH>` | Database directory (default `~/.local/share/virittaa`) |
Analysis saves to the database unless `--no-store`, and skips files that already
have both tags unless `--force`.
```sh
virittaa -tf ~/music/ # analyze, overwrite existing tags
virittaa -j 4 --no-store track.mp3 # 4 threads, nothing stored
virittaa --list --limit 0 # whole database
virittaa --query 'Aphex Twin - Xtal'
virittaa --search '^Aphex'
virittaa --key 'C Major'
virittaa --bpm 120 140
virittaa --import-mixxx ~/.mixxx/mixxxdb.sqlite
```