Add the step-by-step fixer and library types
Each pair is a Music (Artist/Album/01 Title) or Soundtracks (Album/01 Title) library. The new Fixer tab takes the master, then the slave, through four reviewed steps: folder structure, album tags (optional MusicBrainz lookup; the slave reuses the fixed master's tags), folder names and file names, then links the two libraries. Every fix is an undoable operation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b11d0806c1
commit
428935dc58
19 files changed
+3503
-23
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -79,6 +80,22 @@ func DefaultNaming() NamingConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// Library types. Music is organized Artist/Album/01 Title; soundtracks (games,
|
||||
// films) Album/01 Title, since their "artist" is often a composer or empty.
|
||||
const (
|
||||
LibraryMusic = "music"
|
||||
LibrarySoundtracks = "soundtracks"
|
||||
)
|
||||
|
||||
// DefaultNamingFor returns the recommended naming for a library type.
|
||||
func DefaultNamingFor(libraryType string) NamingConfig {
|
||||
c := DefaultNaming()
|
||||
if libraryType == LibrarySoundtracks {
|
||||
c.ArtistFolder = ""
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// SoundtrackFolder is the suggested folder rule for game and film soundtracks.
|
||||
var SoundtrackFolder = FolderNaming{Folder: "Video Games", AlbumFolder: "{Album Title}",
|
||||
TrackFormat: "{track:00} {Track Title}", MultiDiscFormat: "{Medium Format} {medium:00}/{track:00} {Track Title}"}
|
||||
@@ -103,7 +120,7 @@ var NamingPresets = []struct {
|
||||
// NamingOf returns a pair's naming, converting the settings of earlier
|
||||
// versions (one path pattern plus a disc style) when none is stored yet.
|
||||
func NamingOf(p store.Pair) NamingConfig {
|
||||
c := DefaultNaming()
|
||||
c := DefaultNamingFor(p.LibraryType)
|
||||
if len(p.OrganizeConfig) > 0 && string(p.OrganizeConfig) != "{}" {
|
||||
if stored, err := parseNaming(p.OrganizeConfig); err == nil {
|
||||
return stored
|
||||
@@ -139,7 +156,7 @@ func NamingOf(p store.Pair) NamingConfig {
|
||||
// parseNaming reads stored settings. Fields missing from the JSON take the
|
||||
// defaults; an artist folder that is present but empty stays empty.
|
||||
func parseNaming(raw []byte) (NamingConfig, error) {
|
||||
c := DefaultNaming()
|
||||
c := DefaultNaming() // missing keys: only very old or hand-made settings
|
||||
if err := json.Unmarshal(raw, &c); err != nil {
|
||||
return c, err
|
||||
}
|
||||
@@ -147,6 +164,17 @@ func parseNaming(raw []byte) (NamingConfig, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ChangeLibraryType switches a pair's naming to the new type's recommended
|
||||
// naming when it still uses the old type's, so custom naming is kept.
|
||||
func ChangeLibraryType(old store.Pair, p *store.Pair) {
|
||||
if old.ID == 0 || old.LibraryType == p.LibraryType {
|
||||
return
|
||||
}
|
||||
if reflect.DeepEqual(NamingOf(*p), DefaultNamingFor(old.LibraryType)) {
|
||||
p.OrganizeConfig = nil
|
||||
}
|
||||
}
|
||||
|
||||
func fill(c *NamingConfig, d NamingConfig) {
|
||||
c.ArtistFolder = strings.TrimSpace(c.ArtistFolder)
|
||||
if c.Folders == nil {
|
||||
|
||||
Reference in new issue
Block a user