mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-07-27 23:42:28 +02:00
feat: infer beatport release artist
This commit is contained in:
@@ -480,14 +480,16 @@ func (c *Client) rawRequest(ctx context.Context, method, endpoint string, params
|
|||||||
|
|
||||||
func (c *Client) normalizeRelease(raw map[string]any, tracks []any) map[string]any {
|
func (c *Client) normalizeRelease(raw map[string]any, tracks []any) map[string]any {
|
||||||
id := jsonutil.StringFromAny(raw["id"])
|
id := jsonutil.StringFromAny(raw["id"])
|
||||||
artist := c.releaseArtistName(raw["artists"])
|
artist := c.releaseArtistNameForTracks(raw["artists"], tracks)
|
||||||
|
trackRelease := cloneMap(raw)
|
||||||
|
trackRelease["album_artist_name"] = artist
|
||||||
items := make([]any, 0, len(tracks))
|
items := make([]any, 0, len(tracks))
|
||||||
for _, entry := range tracks {
|
for _, entry := range tracks {
|
||||||
track, ok := entry.(map[string]any)
|
track, ok := entry.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
track["release"] = raw
|
track["release"] = trackRelease
|
||||||
items = append(items, c.normalizeTrack(track))
|
items = append(items, c.normalizeTrack(track))
|
||||||
}
|
}
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
@@ -510,7 +512,10 @@ func (c *Client) normalizeTrack(raw map[string]any) map[string]any {
|
|||||||
trackArtistNames := artistNames(raw["artists"])
|
trackArtistNames := artistNames(raw["artists"])
|
||||||
artistName := displayArtistNames(trackArtistNames)
|
artistName := displayArtistNames(trackArtistNames)
|
||||||
artistID := firstArtistID(raw["artists"])
|
artistID := firstArtistID(raw["artists"])
|
||||||
albumArtist := c.releaseArtistName(release["artists"])
|
albumArtist := strings.TrimSpace(jsonutil.StringFromAny(release["album_artist_name"]))
|
||||||
|
if albumArtist == "" {
|
||||||
|
albumArtist = c.releaseArtistName(release["artists"])
|
||||||
|
}
|
||||||
if albumArtist == "" {
|
if albumArtist == "" {
|
||||||
albumArtist = artistName
|
albumArtist = artistName
|
||||||
}
|
}
|
||||||
@@ -693,6 +698,71 @@ func (c *Client) releaseArtistName(v any) string {
|
|||||||
return displayArtistNames(artistNames(v))
|
return displayArtistNames(artistNames(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) releaseArtistNameForTracks(releaseArtists any, tracks []any) string {
|
||||||
|
common := commonReleaseArtists(releaseArtists, tracks)
|
||||||
|
if len(common) > 0 {
|
||||||
|
return displayArtistNames(artistNames(common))
|
||||||
|
}
|
||||||
|
return c.releaseArtistName(releaseArtists)
|
||||||
|
}
|
||||||
|
|
||||||
|
func commonReleaseArtists(releaseArtists any, tracks []any) []any {
|
||||||
|
items := sliceAny(releaseArtists)
|
||||||
|
if len(items) == 0 || len(tracks) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
counts := map[string]int{}
|
||||||
|
trackCount := 0
|
||||||
|
for _, rawTrack := range tracks {
|
||||||
|
track, ok := rawTrack.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
trackCount++
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, rawArtist := range sliceAny(track["artists"]) {
|
||||||
|
if key := artistIdentity(rawArtist); key != "" {
|
||||||
|
seen[key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for key := range seen {
|
||||||
|
counts[key]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if trackCount == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
common := make([]any, 0)
|
||||||
|
for _, rawArtist := range items {
|
||||||
|
if key := artistIdentity(rawArtist); key != "" && counts[key] == trackCount {
|
||||||
|
common = append(common, rawArtist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return common
|
||||||
|
}
|
||||||
|
|
||||||
|
func artistIdentity(raw any) string {
|
||||||
|
m, ok := raw.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if id := strings.TrimSpace(jsonutil.StringFromAny(m["id"])); id != "" {
|
||||||
|
return "id:" + id
|
||||||
|
}
|
||||||
|
if name := strings.TrimSpace(jsonutil.StringFromAny(m["name"])); name != "" {
|
||||||
|
return "name:" + strings.ToLower(name)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneMap(in map[string]any) map[string]any {
|
||||||
|
out := make(map[string]any, len(in))
|
||||||
|
for k, v := range in {
|
||||||
|
out[k] = v
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func firstArtistID(v any) string {
|
func firstArtistID(v any) string {
|
||||||
items := sliceAny(v)
|
items := sliceAny(v)
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func TestAlbumMetadataCollapsesManyReleaseArtists(t *testing.T) {
|
|||||||
writeJSON(t, w, map[string]any{
|
writeJSON(t, w, map[string]any{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"name": "Compilation",
|
"name": "Compilation",
|
||||||
"track_count": 1,
|
"track_count": 2,
|
||||||
"artists": []any{
|
"artists": []any{
|
||||||
map[string]any{"id": 1, "name": "A"},
|
map[string]any{"id": 1, "name": "A"},
|
||||||
map[string]any{"id": 2, "name": "B"},
|
map[string]any{"id": 2, "name": "B"},
|
||||||
@@ -125,12 +125,10 @@ func TestAlbumMetadataCollapsesManyReleaseArtists(t *testing.T) {
|
|||||||
case "/catalog/releases/7/tracks/":
|
case "/catalog/releases/7/tracks/":
|
||||||
writeJSON(t, w, map[string]any{
|
writeJSON(t, w, map[string]any{
|
||||||
"next": nil,
|
"next": nil,
|
||||||
"results": []any{map[string]any{
|
"results": []any{
|
||||||
"id": 42,
|
map[string]any{"id": 42, "name": "Track A", "number": 1, "artists": []any{map[string]any{"id": 1, "name": "A"}}},
|
||||||
"name": "Track Name",
|
map[string]any{"id": 43, "name": "Track B", "number": 2, "artists": []any{map[string]any{"id": 2, "name": "B"}}},
|
||||||
"number": 1,
|
},
|
||||||
"artists": []any{map[string]any{"id": 1, "name": "A"}},
|
|
||||||
}},
|
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected path %q", r.URL.Path)
|
t.Fatalf("unexpected path %q", r.URL.Path)
|
||||||
@@ -154,6 +152,52 @@ func TestAlbumMetadataCollapsesManyReleaseArtists(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAlbumMetadataUsesArtistPresentOnEveryTrack(t *testing.T) {
|
||||||
|
c, closeServer := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/catalog/releases/7/":
|
||||||
|
writeJSON(t, w, map[string]any{
|
||||||
|
"id": 7,
|
||||||
|
"name": "Artist Album",
|
||||||
|
"track_count": 3,
|
||||||
|
"artists": []any{
|
||||||
|
map[string]any{"id": 1, "name": "Main"},
|
||||||
|
map[string]any{"id": 2, "name": "Guest A"},
|
||||||
|
map[string]any{"id": 3, "name": "Guest B"},
|
||||||
|
map[string]any{"id": 4, "name": "Guest C"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
case "/catalog/releases/7/tracks/":
|
||||||
|
writeJSON(t, w, map[string]any{
|
||||||
|
"next": nil,
|
||||||
|
"results": []any{
|
||||||
|
map[string]any{"id": 42, "name": "Track A", "number": 1, "artists": []any{map[string]any{"id": 1, "name": "Main"}, map[string]any{"id": 2, "name": "Guest A"}}},
|
||||||
|
map[string]any{"id": 43, "name": "Track B", "number": 2, "artists": []any{map[string]any{"id": 1, "name": "Main"}, map[string]any{"id": 3, "name": "Guest B"}}},
|
||||||
|
map[string]any{"id": 44, "name": "Track C", "number": 3, "artists": []any{map[string]any{"id": 1, "name": "Main"}, map[string]any{"id": 4, "name": "Guest C"}}},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected path %q", r.URL.Path)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer closeServer()
|
||||||
|
|
||||||
|
meta, err := c.GetMetadata(context.Background(), "7", "album")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetMetadata() error = %v", err)
|
||||||
|
}
|
||||||
|
artist := meta["artist"].(map[string]any)
|
||||||
|
if artist["name"] != "Main" {
|
||||||
|
t.Fatalf("album artist = %q, want Main", artist["name"])
|
||||||
|
}
|
||||||
|
track := meta["tracks"].(map[string]any)["items"].([]any)[0].(map[string]any)
|
||||||
|
album := track["album"].(map[string]any)
|
||||||
|
albumArtist := album["artist"].(map[string]any)
|
||||||
|
if albumArtist["name"] != "Main" {
|
||||||
|
t.Fatalf("track album artist = %q, want Main", albumArtist["name"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReleaseArtistCollapseCanBeDisabled(t *testing.T) {
|
func TestReleaseArtistCollapseCanBeDisabled(t *testing.T) {
|
||||||
cfg := &config.Config{Session: config.DefaultConfigData(), File: config.DefaultConfigData()}
|
cfg := &config.Config{Session: config.DefaultConfigData(), File: config.DefaultConfigData()}
|
||||||
cfg.Session.Beatport.VariousArtistsThreshold = -1
|
cfg.Session.Beatport.VariousArtistsThreshold = -1
|
||||||
|
|||||||
Reference in New Issue
Block a user