From 5d0decbb2a94fcff7ff184eb2d8060b4adcbdaa5 Mon Sep 17 00:00:00 2001 From: mrleo1nid <43583385+mrleo1nid@users.noreply.github.com> Date: Tue, 3 Nov 2026 23:14:48 +0300 Subject: [PATCH] Use the primary artist for upload destination folders Co-Authored-By: Claude Sonnet 5.5 --- services/api/internal/uploads/model.go | 13 ++++++++++++- services/api/internal/uploads/placement.go | 2 +- services/api/internal/uploads/sidecar_test.go | 6 ++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/services/api/internal/uploads/model.go b/services/api/internal/uploads/model.go index a8f9292..dab44ed 100644 --- a/services/api/internal/uploads/model.go +++ b/services/api/internal/uploads/model.go @@ -81,6 +81,17 @@ type Meta struct { Disc *int `json:"disc,omitempty"` } +// PrimaryArtist — первый исполнитель альбома (при пустом album artist — трека; « & » имя не делит): «A;B» и «A feat. B» +// лежат в папке «A», как и в каталоге, где альбом принадлежит первому исполнителю. +func (m Meta) PrimaryArtist() string { + for _, field := range []string{m.AlbumArtist, m.Artist} { + if credits := meta.SplitArtists(field, func(string) bool { return true }); len(credits) > 0 { + return credits[0].Name + } + } + return "" +} + // Fields — значения для шаблона пути. func (m Meta) Fields() meta.Fields { num := func(p *int) string { @@ -90,7 +101,7 @@ func (m Meta) Fields() meta.Fields { return strconv.Itoa(*p) } return meta.Fields{ - Artist: m.Artist, AlbumArtist: m.AlbumArtist, Album: m.Album, Title: m.Title, Genre: m.Genre, + Artist: m.Artist, AlbumArtist: m.PrimaryArtist(), Album: m.Album, Title: m.Title, Genre: m.Genre, Year: num(m.Year), Track: num(m.Track), Disc: num(m.Disc), } } diff --git a/services/api/internal/uploads/placement.go b/services/api/internal/uploads/placement.go index 9a595e0..d4b15ab 100644 --- a/services/api/internal/uploads/placement.go +++ b/services/api/internal/uploads/placement.go @@ -123,7 +123,7 @@ func (s *PlacementPlanner) plan(ctx context.Context, f File, m Meta, chosen *uui pl placement discs int // дисков в альбоме: в каталоге, в пачке, у самого файла ) - if artist, album, maxDisc, err := s.store.MatchCatalog(ctx, meta.Key(m.AlbumArtist), meta.Key(m.Album)); err == nil { + if artist, album, maxDisc, err := s.store.MatchCatalog(ctx, meta.Key(m.PrimaryArtist()), meta.Key(m.Album)); err == nil { pl.ArtistID, pl.AlbumID, discs = artist, album, maxDisc } src, reason := s.pickSource(ctx, chosen) diff --git a/services/api/internal/uploads/sidecar_test.go b/services/api/internal/uploads/sidecar_test.go index f8fa1f6..fa1395a 100644 --- a/services/api/internal/uploads/sidecar_test.go +++ b/services/api/internal/uploads/sidecar_test.go @@ -24,3 +24,9 @@ func TestSidecarDirs(t *testing.T) { require.Empty(t, album) require.Empty(t, artist) } + +func TestPrimaryArtistKeepsFeaturingInFirstArtistFolder(t *testing.T) { + require.Equal(t, "BOULEVARD DEPO", Meta{AlbumArtist: "BOULEVARD DEPO;JEEMBO"}.PrimaryArtist()) + require.Equal(t, "BOULEVARD DEPO", Meta{Artist: "BOULEVARD DEPO feat. SP4K"}.PrimaryArtist()) + require.Equal(t, "Simon & Garfunkel", Meta{AlbumArtist: "Simon & Garfunkel"}.Fields().AlbumArtist) +}