diff --git a/internal/services/media_scanner.go b/internal/services/media_scanner.go index fc7f0991..d68eda82 100644 --- a/internal/services/media_scanner.go +++ b/internal/services/media_scanner.go @@ -1884,11 +1884,17 @@ func extractAudiobookshelfSidecar(path string) *MediaMetadata { } // Smart genre (A2c ยง2.5): most-specific canonical genre wins the genre - // column; every candidate (split parts included) stays in tags. - if genre, _ := pickSmartGenre(sidecar.Genres); genre != "" { + // column; every candidate (split parts included) stays in tags. The + // split is the point: ABS-style exports join genres into one + // comma-string ("Epic, Action & Adventure, Military") which must land + // as separate tags, while commas inside a single subject heading stay + // legal within one tag. + genre, all := pickSmartGenre(sidecar.Genres) + if genre != "" { metadata.Genre = genre } - if tags := append(append([]string{}, sidecar.Genres...), sidecar.Tags...); len(tags) > 0 { + tags := append(append([]string{}, all...), sidecar.Tags...) + if len(tags) > 0 { metadata.Tags = utils.NormalizeTags(tags) } if sidecar.Language != nil { diff --git a/internal/services/media_scanner_metadata_test.go b/internal/services/media_scanner_metadata_test.go index aea9c8db..baeffc8a 100644 --- a/internal/services/media_scanner_metadata_test.go +++ b/internal/services/media_scanner_metadata_test.go @@ -8,6 +8,7 @@ import ( "image/jpeg" "os" "path/filepath" + "strings" "testing" "time" ) @@ -196,6 +197,30 @@ func TestExtractAudiobookshelfSidecar(t *testing.T) { } }, }, + { + name: "joined genre string splits into separate tags (ABS export)", + json: `{"title": "Joined Genres", "genres": ["Epic, Action & Adventure, Military, Sword & Sorcery"]}`, + validate: func(t *testing.T, m *MediaMetadata) { + want := []string{"Epic", "Action & Adventure", "Military", "Sword & Sorcery"} + if len(m.Tags) != len(want) { + t.Fatalf("Tags = %v, want %v", m.Tags, want) + } + for i, w := range want { + if m.Tags[i] != w { + t.Errorf("Tags[%d] = %q, want %q", i, m.Tags[i], w) + } + } + inGenre := false + for _, w := range want { + if strings.EqualFold(m.Genre, w) { + inGenre = true + } + } + if !inGenre || m.Genre == "" { + t.Errorf("Genre = %q, want a case-insensitive match of %v", m.Genre, want) + } + }, + }, { name: "sparse sidecar (real-world Audiobookshelf export)", json: `{"title": "Sparse (1234)", "authors": ["X"], "tags": [], "description": null}`,