DEV Community

John
John

Posted on

Navidrome duplicate albums, split discs and missing art: the tagging and mount anti-patterns that break a 50k library

Navidrome is not guessing at your library, and it is not corrupting it. It builds albums from the tags inside your files, then keys every album and track to a persistent ID derived from those tags, so a duplicate album means two groups of files disagree about ALBUMARTIST, album name, release date or MusicBrainz ID. Split discs, "Various Artists" fragmentation and missing covers come from the same root cause, one layer down: inconsistent tags, or a filesystem that reports different modification times and paths on every mount. Fix the tags and stabilise the mount, and the scanner stops being the problem.

TL;DR by reader profile

  • Solo developer with a 50,000 track library on a home NAS, mounted over SMB: move the music onto a local disk or a stable NFS export before touching tags, because mtime drift makes every scan a full scan and hides whether your tag fixes worked.
  • Anyone whose library came from mixed sources over ten years, ripped CDs plus purchased downloads plus old downloads: run MusicBrainz Picard over the worst offenders first, since missing ALBUMARTIST and inconsistent album titles cause the large majority of duplicate album reports.
  • Beets user who already has a tidy, scripted library: keep beets as the source of truth and point Navidrome at its output read only, because two tools writing tags to the same files is how you get half-updated albums.
  • Docker Compose user running Navidrome next to Jellyfin, Plex or Lidarr on the same folder: decide which application owns tag writes and cover files, since competing artwork conventions are the usual reason embedded art stops showing.
  • Anyone running from a laptop, an external USB drive or a rclone mount: disable the watcher and use a scheduled scan, because filesystem notification events do not cross most network and FUSE mounts reliably.
  • Anyone about to "just delete the database and rescan": export playlists and back up navidrome.db first, because a rebuild recovers metadata but not play counts, ratings or starred items unless the identity tags are stable.

The central tradeoff is this: strict, MusicBrainz backed tags give you a library that survives rescans and server moves, but they cost hours of upfront cleanup that a loose folder based collection never asks for until the day it breaks.


Table of contents


Why does Navidrome show the same album twice after a scan?

Because two sets of files disagree about at least one grouping tag, and Navidrome trusts the tags rather than the folder. It never merges albums by directory name. If ten tracks say ALBUMARTIST=Radiohead and the other two say Radiohead with a trailing space, you get two albums, both named the same, sitting side by side in the interface.

The disagreements that produce duplicates:

  • Album title variants: one track tagged OK Computer and another OK Computer (Remastered) are two different releases as far as the scanner is concerned, and the interface offers no visual hint that only the suffix differs.
  • Partial ALBUMARTIST coverage: if a tag editor wrote ALBUMARTIST on eight of twelve tracks, the untagged four fall back to their own ARTIST value, so a guest vocalist on track 9 becomes an album of one.
  • Mixed MusicBrainz IDs: files carrying MUSICBRAINZ_ALBUMID are keyed by that ID, while files without it are keyed by artist plus title plus release data, so a half tagged album cannot group with itself.
  • Release date and version drift: a differing DATE, ORIGINALDATE or release version field separates two otherwise identical halves of the same rip.
  • Invisible characters: trailing whitespace, curly versus straight apostrophes and non breaking spaces compare as different strings, which is why the two entries look identical on screen.

Check one before guessing. Run ffprobe -show_entries format_tags -v quiet yourfile.flac on a track from each duplicate and compare the output line by line. In most cases the answer is visible within 30 seconds.


How Navidrome decides what an album is: persistent IDs and the grouping tags

Every album and every track gets a persistent ID, computed by hashing a configurable expression made of tag values. Two files that produce the same album PID are the same album. Two files that do not, are not. That is the whole rule, and once you can read the expression, duplicate reports stop being mysterious.

The mechanics you need to know:

  • The expression lives in configuration: PID.Album and PID.Track in navidrome.toml, or ND_PID_ALBUM and ND_PID_TRACK as environment variables in your Compose file, so you can inspect and change the grouping rule without touching a single audio file.
  • A comma concatenates, a pipe falls back: musicbrainz_albumid|albumartistid,album,albumversion,releasedate means "use the MusicBrainz album ID if present, otherwise build a key from album artist, album title, version and release date".
  • The first non empty field wins: this is why a library with MusicBrainz IDs on 60 percent of files behaves as two libraries under one roof, with two different keying strategies running in parallel.
  • Track PIDs hang off album PIDs: the default track expression falls back to the album ID plus disc number, track number and title, so an unstable album PID makes every track ID unstable too.
  • Changing the expression is not free: new PIDs mean new rows, and play counts, ratings and starred flags are attached to the old IDs.

Practical consequence: prefer fixing tags over rewriting ND_PID_ALBUM. Loosening the expression to hide a duplicate also merges genuinely distinct releases, such as a 1997 pressing and its 2017 remaster.


Why missing ALBUMARTIST breaks more than any other single tag

Because it is the only tag that tells Navidrome who owns the album, and when it is absent the scanner falls back to the per track ARTIST value. On a solo record that fallback is harmless. On anything with a featured guest, a duet, a remixer or an orchestra credit, it shatters one album into several one track albums, each filed under a different artist in the sidebar.

The tag has a different name in every container, which is exactly why editors write it inconsistently: TPE2 in ID3v2 for MP3, ALBUMARTIST as a Vorbis comment in FLAC and Ogg, and aART in MP4 for AAC and ALAC files.

File situation What Navidrome shows Underlying reason
ALBUMARTIST on all 12 tracks 1 album, 12 tracks Every file produces the same album PID
ALBUMARTIST on 10 of 12 tracks 1 album plus 2 strays The 2 untagged files key off their own ARTIST
No ALBUMARTIST, 4 guest features 5 albums, same cover Each distinct ARTIST value becomes its own key
No ALBUMARTIST on an 18 track compilation Up to 18 albums Every track has a different performer
ALBUMARTIST with a trailing space on 1 file 2 albums, identical names String comparison is exact, whitespace included

Fix it in bulk rather than by hand. In MusicBrainz Picard, matched releases write album artist automatically. In Kid3 or Mp3tag, select a folder and set the field once for every file in it. The rule that survives contact with a real library: ARTIST describes the performance, ALBUMARTIST describes the shelf it sits on, and it must be identical across every file in the album.


Do you need MusicBrainz IDs in Navidrome, and what actually breaks without them?

No. A library with clean, consistent text tags works fine. What you cannot do is mix the two approaches inside one album, because the pipe fallback in the album PID expression means tagged files key off a 36 character UUID and untagged files key off artist plus title plus date. Half a release with MUSICBRAINZ_ALBUMID is the single most reliable way to manufacture a duplicate that no amount of retyping the album name will fix.

What the IDs buy you once coverage is complete:

  • Immunity to text edits: rename an album from Vol. 1 to Volume 1 and the grouping survives, because the key is the UUID and not the string you just changed.
  • Artist disambiguation: three different bands called Nirvana get three MUSICBRAINZ_ARTISTID values, so they stop collapsing into one crowded artist page.
  • Correct external lookups: artist images, biographies and ListenBrainz submissions resolve against a canonical entity rather than a fuzzy name match.
  • Reliable re tagging later: with IDs embedded, beet mbsync or a Picard reload updates metadata from upstream without you re matching anything by hand.
  • A stable identity across servers: move the library to a new machine, rebuild the database, and the albums come back grouped the same way.

The cost is real. Matching a large collection in Picard is manual work on anything ambiguous: bootlegs, live recordings, regional pressings and unofficial releases often have several candidate releases, or none. A pragmatic split is to tag the 20 percent of albums you actually play, leave the rest on text tags, and never mix within one album folder.


Why does one album split into CD1 and CD2 in the interface?

The folder layout is not the problem. Navidrome will happily assemble a 2 disc, 24 track release from Album/CD1 and Album/CD2, because it groups by tags and ignores directory structure. The split happens when the ripper baked the disc number into the album title, or left the disc tag empty.

The four causes, in the order you should check them:

  • Disc suffixes inside the ALBUM tag: files reading The Wall (Disc 1) and The Wall (Disc 2) produce two different album keys, and the sidebar shows two covers with almost identical names. Strip the suffix so both discs carry the exact string The Wall.
  • Missing DISCNUMBER: the tag is DISCNUMBER in FLAC and Ogg, TPOS in ID3v2 and the disk atom in MP4. Without it, disc 2 track 1 and disc 1 track 1 both claim position 1, so the album lists two tracks numbered 1 and the play order goes wrong.
  • Release version or date drift between discs: if one folder was tagged in a later session with a different DATE or release version, the grouping expression sees two releases even though the album titles match.
  • Per disc MusicBrainz IDs: some releases are modelled as separate media within one release, and a mismatched MUSICBRAINZ_ALBUMID on one folder keeps the halves apart permanently.

The bulk fix is a regular expression action in Mp3tag or Kid3 over the parent folder: move the trailing (Disc N) or CDN out of the album field and into the disc number field. Then set DISCTOTAL to 2 on all 24 files so the interface labels the discs correctly.


Compilations, Various Artists and soundtracks: four ways they fragment

A 40 track compilation is the worst case for a tag driven scanner: every track has a different performer, so anything less than perfectly consistent album level tags produces dozens of one track albums. Four distinct failures account for nearly all of it.

  • The compilation flag is missing: the tag is COMPILATION=1 as a Vorbis comment, TCMP in ID3v2 and the cpil atom in MP4. Set it on every file of the release. Without it, nothing marks the album as a multi artist release, and the scanner has only the per track artist to work from.
  • Various Artists is spelled four different ways: Various, Various Artists, VA and V/A are four separate album artists and therefore four separate albums. Pick the exact string Various Artists, which is what MusicBrainz Picard writes, and apply it to all 40 files without exception.
  • Per track ALBUMARTIST was written by a helpful editor: some tools copy ARTIST into ALBUMARTIST when the field is blank, so track 7 becomes an album by that one performer. This is silent, and it happens to files you never opened in the editor yourself.
  • Soundtracks credited to the composer on some tracks only: a film score with 6 licensed songs mixed into 30 cues ends up half credited to the composer and half to the featured bands, splitting one release into two or more.

Verify the whole folder in one pass rather than spot checking. Run ffprobe -show_entries format_tags -v quiet over each file and confirm that album artist, album title and compilation flag are byte identical across all of them.


Why is cover art missing when the file clearly has an embedded image?

Because Navidrome looks for artwork in a configured order, and stops at the first hit. That order is set by CoverArtPriority, or ND_COVERARTPRIORITY in your Compose file, and the default puts embedded art first, then cover.*, folder.* and front.* files in the album directory. If a stale, low resolution folder.jpg outranks your new embedded image under a modified priority string, the old picture wins and nothing looks broken in the logs.

The causes worth checking, in order of how often they turn out to be the real one:

  • The cache folder is not writable: Navidrome resizes artwork and stores it under the cache directory. If the container runs as UID 1000 and the volume is owned by root, extraction silently produces nothing. Run ls -ln on the cache path and confirm the owner matches the user in your Compose file.
  • Art is embedded in one track only: the album cover comes from the files themselves, so tagging track 1 and leaving the other 11 bare gives inconsistent results in track level views.
  • Filename case and spelling: Folder.jpg, AlbumArt.jpg and cover.jpeg are not the same as the patterns in your priority list. Standardise on lowercase cover.jpg in every album directory.
  • The image is not a format the extractor handles: WebP and progressive or CMYK JPEG files embedded by a third party tool are common failures. Re embed as a baseline JPEG or PNG.
  • The browser cached the placeholder: after fixing tags, hard reload the page before concluding the fix failed.

Change one variable at a time, then rescan a single album folder rather than the whole library.


Why does Navidrome rescan the whole library on an SMB or NFS mount?

A quick scan compares each directory's modification time against the timestamp of the last scan. Anything newer gets re read. Network filesystems break that comparison in two ways: they report modification times with different precision or a different timezone offset than the local disk, and they can change the reported value on remount. When every folder looks newer than the last scan, every folder is rescanned, and a 50,000 track library never gets a fast pass.

Mount type mtime stability across remounts Practical consequence
Local ext4 or XFS Stable to the nanosecond Quick scans stay quick, watcher works
NFSv4 with synced clocks Stable if server and client run NTP Reliable, watcher events unreliable
SMB3 via cifs Depends on mount options and server Frequent full rescans, timestamp drift
rclone or other FUSE mount Often synthesised or coarse Repeated full rescans, high latency per file

Diagnose it before changing anything. Run stat -c '%y %n' /music/SomeAlbum from inside the container, unmount and remount the share, then run the identical command again. If the two values differ, the mount is the cause and no amount of tag editing will help.

The fixes that hold, in order of preference:

  • Move the library to a local disk: the only option that removes the class of problem entirely.
  • Pin the SMB mount options: set vers=3.0 and keep noserverino out of your fstab line, since it also destabilises inode numbers.
  • Run NTP on both ends: a clock skew of a few seconds, or a 3600 second daylight saving shift, is enough to invalidate the comparison.
  • Schedule scans instead of fighting the watcher: covered next, but the mount is the thing to fix first.

Symlinks, bind mounts and container paths: the anti-patterns that hide half a library

Navidrome can only index what it can see and read from inside its own namespace. In Docker that namespace is whatever you bind mounted, so a path that works perfectly in your host shell can be empty, dangling or unreadable to the process doing the scanning. The symptom is not an error. It is an album count that is 8,000 tracks short of what you expected.

  • Symlinks that resolve outside the bind mount: if /mnt/music/rock is a link to /mnt/tank/rock on the host and you only mounted /mnt/music, the container sees a broken link. Mount the real target as well, or replace the symlink with a second bind mount in your Compose file.
  • Directories the container user cannot traverse: a folder with mode 700 owned by root is invisible to a process running as UID 1000, and the scan skips it without stopping. Verify with docker exec -u 1000 navidrome ls /music/Artist rather than trusting your own shell.
  • The data folder living inside the music folder: if navidrome.db and the cache sit under the scanned tree, the scanner walks its own working files. Keep music at /music and state at /data, as two separate volumes.
  • Renaming the mount point between deployments: switching from /music to /media/music changes every stored path, so the next scan re imports the entire library even though not one audio file changed.
  • Folders you never wanted indexed: drop a .ndignore file into a directory to exclude it and its children, which is the clean way to skip incoming downloads or a lossless archive.

Top comments (0)