Add organizing files into Artist/Album/01 Title from their tags

A new Organize tab renames audio files from their tags into a per-pair
naming pattern, by default {artist}/{album}/{track} {title} (album
artist, falling back to artist). Multi-disc albums get a disc prefix
(2-04) or a Disc N folder; names are made safe for Windows/SMB shares.

- Preview first: every rename, files already organized, and files
  skipped with the reason (missing tags, with a button into the tag
  editor; two files resolving to the same name; an existing file).
- Applying re-checks each file against the preview, moves it within its
  library (hardlinks and contents untouched, case-only renames via a
  temporary name, completed on startup after a crash), carries .lrc
  lyrics and folder cover art along, and removes emptied folders. Each
  move is an operation that can be undone; the library is rescanned.
- Optional automatic organizing of the slave, master or both during
  scans (off by default). Dry run applies.
- Scans now recognise a slave file that is a hardlink of a master file
  at a different path as linked ("same file, other path") instead of
  unmatched.

Migration 7 adds the per-pair naming settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-29 12:23:41 -07:00
1 parent 3eb02e2c8d
commit dbcf562f7e
16 files changed
+1480 -11

No files matched your search

+7
View File
@@ -43,6 +43,10 @@ type Engine struct {
mu sync.Mutex
tagTools tags.Tools
tagCache tagCache
plansMu sync.Mutex
plans map[int64]*OrganizePlan // latest organize preview per pair
jobsMu sync.Mutex
jobs map[int64]*Job
@@ -290,6 +294,9 @@ func (e *Engine) NormalizePair(p *store.Pair) ([]store.Warning, error) {
if p.RetentionDays < 0 || p.ScanIntervalMinutes < 0 || p.SettleMinutes < 0 {
return nil, errors.New("intervals must not be negative")
}
if err := normalizeOrganize(p); err != nil {
return nil, err
}
if p.ApproxThreshold == 0 {
p.ApproxThreshold = 85
}