# beets configuration — managed by Ansible (roles/navidrome) # https://beets.readthedocs.io/ # # Run it by hand, over SSH, when material has accumulated in {{ navidrome_incoming_dir }}: # beet import {{ navidrome_incoming_dir }} # # It is interactive on purpose. `beet import -q` auto-applies only strong # matches and leaves the rest where they are, which makes a usable review queue # if the batches ever get boring. directory: {{ navidrome_music_dir }} library: {{ navidrome_beets_dir }}/library.db # $albumartist, NOT $artist. This is the single most important line in the # file: organising by $artist scatters every compilation and every "feat." # track into its own phantom artist, which is how a library ends up with four # hundred one-album artists and Navidrome gets blamed for it. paths: default: $albumartist/$album%aunique{}/$track $title singleton: Non-Album/$artist/$title comp: Compilations/$album%aunique{}/$track $title albumtype:soundtrack: Soundtracks/$album/$track $title import: # MOVES files out of {{ navidrome_incoming_dir }} into the library. Set # `copy: yes` and `move: no` instead if you would rather keep originals # until you trust it. move: yes write: yes languages: en es log: {{ navidrome_beets_dir }}/import.log plugins: chroma fetchart embedart scrub duplicates mbsync missing info lastgenre replaygain lyrics permissions # THE trap this library is exposed to, and the fix for it. # # beets MOVES files into place, and rename(2) preserves the original group and # mode - so the setgid bit on {{ navidrome_music_dir }} does NOT apply to them # (it only affects files CREATED there, such as the album directories beets # makes, which do come out group-{{ navidrome_group }}). A track that arrives # mode 0600 therefore lands mode 0600 owned by the curator's own group, and # Navidrome cannot read it. It does not error: the track is simply ABSENT from # the library, which is indistinguishable from never having imported it. # # Measured on this box: a 0600 file was unreadable by the navidrome user; the # same file at 0644 was fine. So normalise every imported file. # NO leading zeros. The plugin does int(str(value), 8), and YAML would parse # `0644` as octal -> decimal 420, which the plugin then re-reads as octal 0o420. # Verified on the box: `0644` silently left a 0600 file at 0600; `644` fixes it. permissions: file: 644 dir: 755 # Acoustic fingerprinting: identifies a file from the audio itself when the # tags are too mangled to match. Needs fpcalc, installed by the role. chroma: auto: yes fetchart: auto: yes embedart: auto: yes # Strip junk and stale tags before writing clean ones. scrub: auto: yes lastgenre: auto: yes source: album # auto: no on both of these deliberately. This box has one core; ReplayGain # analysis and lyrics fetching during an import turn a two-minute job into a # twenty-minute one. Run them separately, after the import, when nothing is # waiting on you: # beet replaygain # beet lyrics replaygain: auto: no backend: ffmpeg lyrics: auto: no