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:
1 parent
3eb02e2c8d
commit
dbcf562f7e
16 files changed
+1480
-11
No files matched your search
@@ -625,6 +625,17 @@ func (e *Engine) Undo(ctx context.Context, opID int64) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.Action == store.ActMove {
|
||||
if err := undoMove(l, o); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.st.MarkUndone(ctx, o.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
e.log.Info("file move undone", "op", o.ID)
|
||||
_, _ = e.StartScan(o.PairID, "undo")
|
||||
return nil
|
||||
}
|
||||
if o.Action == store.ActTags {
|
||||
if err := e.undoTags(ctx, l, o); err != nil {
|
||||
return err
|
||||
@@ -858,6 +869,13 @@ func (e *Engine) Recover(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
for _, o := range ops {
|
||||
if o.Action == store.ActMove {
|
||||
if l, err := e.loadPair(ctx, o.PairID); err == nil {
|
||||
recoverMove(l, o)
|
||||
}
|
||||
_ = e.st.FinishOp(ctx, o.ID, store.OpInterrupted, "interrupted by shutdown; rescan to verify state")
|
||||
continue
|
||||
}
|
||||
if o.Action == store.ActTags {
|
||||
msg := e.recoverTags(ctx, o)
|
||||
_ = e.st.FinishOp(ctx, o.ID, store.OpInterrupted, msg)
|
||||
|
||||
Reference in new issue
Block a user