Andrew Mercer
on this page

What Jellyfin actually cares about

Jellyfin builds its music library from embedded tags first and the filesystem second. Its documentation is explicit that one folder must hold exactly one album, that how albums are grouped above that is up to you, and that filenames generally don't matter because track metadata is scraped from tags; the filename is only used as the track title when no tags are found. Multi-disc albums are identified by the disc number and total discs tags, and tracks can either sit together in one album folder or be split into Disc N subfolders.

There are three filesystem rules that do matter regardless of tags. The characters <, >, :, ", /, \, |, ? and * are known to cause problems in names. Audio-only .mp4 files are not recognised as music and should be .m4a, and audio-only .mkv/.webm should be .mka. Artwork is picked up from files named cover, folder and friends sitting beside the tracks.

So a consistent naming convention is not what makes Jellyfin work; good tags plus one-album-per-folder do that. The convention exists so that untagged files still show a sensible title, so the tree is readable and scriptable, and so problems (wrong folder, missing track numbers, illegal characters) are visible at a glance.

The convention

Music/
├── Artist/
│   ├── folder.jpg                     artist image
│   ├── Album (YYYY)/
│   │   ├── cover.jpg
│   │   ├── 01 - Title.flac
│   │   └── 02 - Title.flac
│   └── Double Album (YYYY)/
│       ├── Disc 1/
│       │   └── 01 - Title.flac
│       └── Disc 2/
│           └── 01 - Title.flac
└── Soundtrack/                        grouping folders are fine
    └── Fight Club/
        └── 06 - Dust Brothers - Psycho Boy Jack.mp3

Track files are NN - Title.ext: a zero-padded track number (three digits once a folder holds 100 or more tracks), a spaced hyphen, the title, and a lower-case extension. The artist and album are not repeated in the filename because the folders already say it. A multi-disc album kept flat in one folder uses D-NN - Title.ext (1-06 - Head Down.flac); one split into Disc N/ folders drops the disc prefix. On compilations and soundtracks where the filename carries a track artist different from the folder's artist, keep it: NN - Track Artist - Title.ext.

Album folders are Album (YYYY), or just Album when the year is unknown. The Artist - YYYY - Album form currently in the library is legal, it just repeats the artist. Disc folders are Disc N, never CD1, CD 2 or Disk 3. A disc folder sitting directly under the artist (The Altogether - CD1 next to The Altogether - CD2) is a separate album as far as the folder rule is concerned, so those belong under one Album/Disc N/ parent.

Illegal characters are replaced rather than deleted where meaning would be lost: Title: Subtitle becomes Title - Subtitle, a double quote becomes a single quote, and ?, *, <, > are dropped.

What's wrong in the current library

The last snapshot of the tree (about 17,700 audio files in 1,689 folders) breaks down roughly like this. Around 10,700 track names need changing, almost all of which are one of the patterns in the table below. About 2,500 files have no track number in the name at all (Echoes.mp3, Sleigh Ride - Ella Fitzgerald.mp3), and those can only be numbered from tags. About 2,500 tracks sit loose in a folder that isn't an album, nearly all of them in USB_Music. There are 34 folders that contain tracks and album subfolders at the same time, roughly 430 file names and 63 folder names containing a colon or another problem character, 29 upper-case extensions, 25 video containers, and a few dozen junk files (.cda CD shortcuts, Thumbs.db, .part, .old).

Pattern found Example Becomes
Artist - Album - NN - Title Acoustic Alchemy - Aart - 01 - Aart.mp3 01 - Aart.mp3
NN Title / NN. Title 07. Blur.mp3, 03 Seein' Red.mp3 07 - Blur.mp3
NN - Artist - Title 06 - The Tragically Hip - So Hard Done By.mp3 06 - So Hard Done By.mp3
Disc N - N - Title Disc 1 - 6 - Head Down.flac 1-06 - Head Down.flac
D.NN Title 2.04 Sacrifice Of Faramir.mp3 2-04 - Sacrifice Of Faramir.mp3
Unpadded number 1 My Kingdom, Pt. 1.mp3 01 - My Kingdom, Pt. 1.mp3
Ripper leftovers 01 _01 Achilles Last Stand.mp3.mp3 01 - Achilles Last Stand.mp3
Scene style 12-finch-bitemarks_and_bloodstains.mp3 12 - bitemarks and bloodstains.mp3
Illegal characters 11 - Error: Operator.flac 11 - Error - Operator.flac
Upper-case extension Wild Colonial Boy.MP3 Wild Colonial Boy.mp3

Before renaming anything

Take a listing so there is a record of the original names independent of any tool:

cd /path/to/Music
tree -a > ~/music-tree-$(date +%F).txt
find . -type f -printf '%P\n' | sort > ~/music-files-$(date +%F).txt

Jellyfin identifies tracks by path, so a rename is seen as one item removed and a new one added. Play counts, favourites and playlist entries attached to renamed tracks are lost. On a library that was only just added that costs nothing, which is a good reason to do this now rather than later. Avoid renaming while a library scan is running, and turn off "Enable real-time monitoring" on the music library for the duration if it's on, so Jellyfin doesn't chase thousands of individual change events.

Fixing names by hand

The manual tool is Perl rename, which applies a Perl substitution to each filename. It is packaged as rename on Debian and Ubuntu, prename on Fedora and RHEL, and perl-rename on Arch. The util-linux rename that ships on Fedora as rename takes a different syntax and will not work with these expressions. Every command below uses -n, which prints what would happen without touching anything; drop -n once the output looks right.

Work one album folder at a time and run the recipes in the order shown, because a later recipe assumes the earlier ones have already normalised the number.

cd "/path/to/Music/Acoustic Alchemy/Aart"

# 1. Disc + track forms first: "Disc 1 - 6 - Title" and "2.04 Title"
prename -n 's/^(?:Disc|CD)\s*(\d+) - (\d+) - /sprintf("%d-%02d - ",$1,$2)/e' *
prename -n 's/^(\d)\.(\d{2}) /$1-$2 - /' *

# 2. Strip a leading "Artist - Album - " (also handles "Artist - 2003 - Album - 6 - Title")
prename -n 's/^.+? - .+? - (\d{1,3}) - /sprintf("%02d - ",$1)/e' *

# 3. "07. Title", "03 Title", "1 Title", "12-title" -> "NN - Title"
prename -n 's/^(\d{1,3})(?:\.(?!\d)\s*|\s+|-(?=[^\s\d]))(?!-\s)/sprintf("%02d - ",$1)/e' *

# 4. Repeated artist after the number (substitute the real artist name)
prename -n 's/^(\d{2,3}) - The Tragically Hip - /$1 - /' *

# 5. Illegal characters
prename -n 's/:\s*/ - /g; s/"/\x27/g; s/[?*<>|]//g' *

# 6. Upper-case and doubled extensions
prename -n 's/\.(MP3|FLAC|M4A|OGG)$/.\L$1/; s/(\.mp3)+$/.mp3/' *

Recipe 2 throws away everything before the track number, which is right for a single-artist album but wrong on a soundtrack where that prefix is the track artist. On compilations, rewrite it to keep the first segment instead:

prename -n 's/^(.+?) - .+? - (\d{1,3}) - /sprintf("%02d - %s - ",$2,$1)/e' *

A track inside a Disc N folder doesn't need the disc prefix, so in disc folders swap recipe 1 for:

prename -n 's/^(?:Disc|CD)\s*\d+ - (\d+) - /sprintf("%02d - ",$1)/e' *

Folders

Album folders named Artist - YYYY - Album become Album (YYYY). Run this from the artist folder; anchoring on the last path component keeps it from touching anything above:

cd "/path/to/Music/Third Eye Blind"
find . -mindepth 1 -maxdepth 1 -type d -exec prename -n 's{([^/]+) - (\d{4}) - ([^/]+)$}{$3 ($2)}' {} +

Disc folders become Disc N, and colons in folder names get the same treatment as in files:

find . -type d -regextype posix-extended -iregex '.*/(cd|disc|disk) ?[0-9]+' \
  -exec prename -n 's{(?i)(cd|disc|disk)\s*0*(\d+)$}{Disc $2}' {} +
find . -depth -type d -name '*:*' -exec prename -n 's{:\s*([^/]*)$}{ - $1}' {} +

The -depth on the last command matters: it renames children before their parents, so paths found earlier in the walk stay valid.

Junk and video files

Junk is safe to delete once you've looked at the list:

find . -type f \( -iname '*.cda' -o -iname 'thumbs.db' -o -iname 'desktop.ini' \
  -o -iname '*.part' -o -iname '*.old' -o -name '._*' \) -print
# when satisfied, append: -delete

Video containers need a decision per file. Real music videos belong in a separate Music Videos library. An audio-only .mp4 can simply be renamed to .m4a; check first with ffprobe -v error -show_streams -select_streams v "file.mp4", which prints nothing when there is no video stream. Audio-only .mkv files should be remuxed with ffmpeg -i in.mkv -c:a copy out.mka.

Loose tracks, mixed folders and untagged files

These aren't naming problems and renaming won't fix them. USB_Music (around 2,200 loose tracks) is a playlist dump, not an album, and the honest fix is to move it out of the library and import it with a tagger that sorts files into Artist/Album/ by their metadata. beets does this from the command line (beet import -A keeps existing tags, beet import matches against MusicBrainz); MusicBrainz Picard does it with a GUI. The same applies to the files with no track number in their name: write the track numbers into the tags with Picard or beets, then the renamer can take it from there. A folder that holds tracks and album subfolders at once (for example an artist folder with a stray single next to its albums) needs those loose tracks moved into an album folder or a Singles folder.

Doing it in bulk with jellyfin-adm

jellyfin-adm applies every rule above across the whole library, prompts before changing anything, and journals each rename so the run can be reversed.

# report only: every planned rename, grouped by folder, plus layout warnings
jellyfin-adm music scan /path/to/Music | less
jellyfin-adm music scan /path/to/Music --folders --summary

# fix tracks, asking once per album folder ([y]es/[n]o/[a]ll/[q]uit)
jellyfin-adm music fix /path/to/Music

# include folder renames, ask per file instead of per folder
jellyfin-adm music fix /path/to/Music --folders --per-file

# no prompts
jellyfin-adm music fix /path/to/Music --folders --yes

# put everything back
jellyfin-adm music undo ~/.local/state/jellyfin-adm/renames-<timestamp>.tsv

The tool never overwrites: a rename whose target exists, or two files that would end up with the same name, are skipped and listed under "Skipped: rename would collide". Files with no recognisable track number only get the safe fixes (illegal characters, whitespace, extension case) and are listed for tagging. Loose tracks, mixed folders, split disc folders, junk and video files are reported, never moved or deleted.

After renaming

Re-enable real-time monitoring if you turned it off, then run Dashboard → Libraries → Scan All Libraries. Spot-check a few albums that had disc folders or compilation artists, and run jellyfin-adm music scan /path/to/Music --summary again: the track-rename count should be zero, and what's left is the list of things that need tags or a human decision.