Skip to main content
  1. Technologies/

Navidrome

·131 words·1 min· ·
Technologies notes - This article is part of a series.
Part 17: This Article

navidrome is a subsonic compatible musing streaming service that relies on metadata to catalog a music library

Manage metadata
#

In order to work properly navidrome needs that tracks have metadata populated, to compile them there are a pletora of software, one of them being picard

Collection file structure
#

To keep things ordered and clean files inside the collection folder are organized as follows

collection/
├── artist 1
│   └── album 1
│       └── track 1
│   └── album 2
│       ├── track 1
│       ├── track 2
│       └── .....
├── artist 2
│   └── album 1
│       ├── track 1
│       └── .....
├── playlists
│   ├── ....nsp
│   ├── ....nsp
│   └── ....nsp
.....

In order to divide tracks in album folder run the following oneliners

💡

href="https://picard.musicbrainz.org/" target="_blank" >picard can move files based on metadata values inside folders so this 2 snippets aren’t needed

this will create folder based on ALBUM metadata

find . -type f | parallel 'ffmpeg -i {} -f ffmetadata' 2>&1 | grep 'ALBUM ' |awk -F':' '{$1=""; print $0}' | while read dir; do
    if [[ -d "$dir" ]]; then
        echo "$dir already exists"
    else
        mkdir -p "$dir";
    fi
done

this will move tracks in ALBUM folder according to metadata

find . -type f | while read f; do ALBUM="$(ffmpeg -i "$f" -f ffmetadata 2>&1 | grep 'ALBUM ' | awk -F':' '{$1="";print $0}' | awk '{$1=$1;print}')";
if [[ -f "$ALBUM/$f" ]]; then
    echo "$ALBUM/$f already exists";
else
    mv "$f" "$ALBUM/";
fi
done

Creating smart playlists
#

Smart playlists are ways of grouping tracks together based on metadata values, they are defined inside json files with the .nsp extension

file last_played.nsp

{
  "name": "Recently Played",
  "comment": "Recently played tracks",
  "all": [
    {"inTheLast": {"lastPlayed": 30}}
  ],
  "sort": "lastPlayed",
  "order": "desc",
  "limit": 100
}

This json files are composed by a sequence of statements in the form

    {"operator": {"field": "value"}}

The tracks with metadata that matches the statement are included inside the smart playlist, here a complete list of fields and operators

Backup physical collection
#

To backup the physical collection the process requires to acquire the audio tracks of a cd in a file format (typically wav) and then edit metadata, under archlinux the utility cdda2wav can do the job

from the arch wiki

cdda2wav -vall cddb=-1 speed=4 -paranoia paraopts=proof -B -D /dev/sr0

❗ Warning

Notes that paranoia parameters make the process slower, for cd that are in good conditions they can be omitted

An handy alias to avoid memorizing parameters

alias ripCD='mkdir temp && cd temp && cdda2wav -vall cddb=-1 speed=4 -paranoia paraopts=proof -B -D /dev/sr0'

Retrieving lyrics for tracks
#

In order to retrieve lyrics for tracks a useful resource is lrclib, which is an online index of lyrics that can be queried using the LRCGET program.

I wrote a simple script that uses the lrclib API to download lyrics for a given directory

#!/bin/bash
source "$HOME/.config/scripts/settings.sh"

declare -A FLAGS
FLAGS[a]='AUDIO_ONLY="-x -f bestaudio"'
FLAGS[c]='SUBS=--embed-subs'
FLAGS[u]='URL=${OPTARG}'
FLAGS[f]='FORMAT="-f "${OPTARG}'
FLAGS[d]='DEST_FOLDER=${OPTARG}'
FLAGS[l]='LINKS_FILE=${OPTARG}'
FLAGS[D]='DEBUG=1'
FLAGS[C]='COLLECTION_DIR=${OPTARG}'
FLAGS_STRING='acf:u:d:l:C:D'

declare -A COMMANDS
COMMANDS[dwl]="download content"
COMMANDS[download]="download audio files from youtube links"
COMMANDS[ripcd]="download lyrics from lrclib.net"

# constants
COLLECTION_DIR="$HOME/collection"
BASEURL="https://lrclib.net/api/get?"
LOG_DIR="$SCRIPTS_LOGS_FOLDER/$(basename $0 .sh)"; if test ! -d $LOG_DIR;then mkdir "$LOG_DIR";fi
NOW="$(date +%s)"
YT_DLP_PROGRESS_FILE='/tmp/progress.txt'
NV_ALBUM_DIR="/tmp/nv_album"

function dwl() {
  if [[ -z $DEST_FOLDER ]] || [[ -z $URL ]]; then help; exit 1; fi
  # setting yt-dlp command
  YT_DLP_CMD="yt-dlp \
    --ignore-errors \
    --continue \
    --no-overwrites \
    --download-archive $YT_DLP_PROGRESS_FILE \
    ${AUDIO_ONLY} \
    ${SUBS} \
    ${FORMAT} \
    -P $DEST_FOLDER \
    $URL"

  # printing command for debugging purposes
  if [[ "$DEBUG" == "1" ]]; then echo "running $YT_DLP_CMD"; fi

  # run command
  $YT_DLP_CMD

  # remove progress file
  rm -f "$YT_DLP_PROGRESS_FILE"
}

function download(){

  if [[ -n $LINKS_FILE ]]; then
  cat "$LINKS_FILE" | while read link; do
    echo "downloading $link"
    ($0 dwl -a -d $NV_ALBUM_DIR/$(uuidgen) -u "$link")
  done
  elif [[ -n $URL ]]; then
    ($0 dwl -a -d $NV_ALBUM_DIR/$(uuidgen) -u "$URL")
  fi

}

function ripcd(){
  mkdir -p "$NV_ALBUM_DIR"
  (
    cd "$NV_ALBUM_DIR"
    cdda2wav -vall cddb=-1 speed=4 -B -D /dev/sr0
  )
}

source "$SCRIPTS_LIB_FOLDER/cli.sh"
Matteo Longhi
Author
Matteo Longhi
I’m a software engineer with a passion for Music, food, dogs, videogames and open source software, i’m currently working as a devops engineer
Technologies notes - This article is part of a series.
Part 17: This Article