diff --git a/internal/app/app.go b/internal/app/app.go index 92a45eb..528e28b 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -11,6 +11,7 @@ import ( "strconv" "strings" "sync" + "time" "streamrip-go/internal/artwork" "streamrip-go/internal/audio/convert" @@ -58,6 +59,7 @@ type ripTrackOptions struct { forPlaylist bool playlistName string playlistPos int + playlistYear int } type folderAudioValues struct { @@ -372,14 +374,22 @@ func (m *Main) ripTrackCollection(ctx context.Context, p provider.Client, source ids = append(ids, id) } } + var artRes artwork.Result + var playlistYear int + if playlistLike { + artRes, _ = artwork.Prepare(ctx, m.DL, folder, meta, m.Config.Session.Artwork, true) + playlistYear = extractYear(meta) + } + m.logf("%s: %s (%d tracks)\n", kind, name, len(ids)) failures := 0 runOne := func(i int, trackID string) { - opts := ripTrackOptions{albumFolder: folder, index: i, total: len(ids)} + opts := ripTrackOptions{albumFolder: folder, albumEmbedCover: artRes.EmbedPath, index: i, total: len(ids)} if playlistLike { opts.forPlaylist = true opts.playlistName = name opts.playlistPos = i + opts.playlistYear = playlistYear } if err := m.ripTrack(ctx, p, source, trackID, "", opts); err != nil { failures++ @@ -404,11 +414,12 @@ func (m *Main) ripTrackCollection(ctx context.Context, p provider.Client, source go func(pos int, tid string) { defer wg.Done() defer func() { <-sem }() - opts := ripTrackOptions{albumFolder: folder, index: pos, total: len(ids)} + opts := ripTrackOptions{albumFolder: folder, albumEmbedCover: artRes.EmbedPath, index: pos, total: len(ids)} if playlistLike { opts.forPlaylist = true opts.playlistName = name opts.playlistPos = pos + opts.playlistYear = playlistYear } if err := m.ripTrack(ctx, p, source, tid, "", opts); err != nil { mu.Lock() @@ -806,18 +817,24 @@ func (m *Main) ripPlaylist(ctx context.Context, p provider.Client, source, playl } } + artRes, _ := artwork.Prepare(ctx, m.DL, folder, playlistMeta, m.Config.Session.Artwork, true) + + playlistYear := extractYear(playlistMeta) + total := len(ids) m.logf("Playlist: %s (%d tracks)\n", name, total) failures := 0 runOne := func(i int, id string) { opts := ripTrackOptions{ - albumFolder: folder, - index: i, - total: total, - forPlaylist: true, - playlistName: name, - playlistPos: i, + albumFolder: folder, + albumEmbedCover: artRes.EmbedPath, + index: i, + total: total, + forPlaylist: true, + playlistName: name, + playlistPos: i, + playlistYear: playlistYear, } if err := m.ripTrack(ctx, p, source, id, "", opts); err != nil { failures++ @@ -843,7 +860,7 @@ func (m *Main) ripPlaylist(ctx context.Context, p provider.Client, source, playl go func(pos int, tid string) { defer wg.Done() defer func() { <-sem }() - opts := ripTrackOptions{albumFolder: folder, index: pos, total: total, forPlaylist: true, playlistName: name, playlistPos: pos} + opts := ripTrackOptions{albumFolder: folder, albumEmbedCover: artRes.EmbedPath, index: pos, total: total, forPlaylist: true, playlistName: name, playlistPos: pos, playlistYear: playlistYear} if err := m.ripTrack(ctx, p, source, tid, "", opts); err != nil { mu.Lock() failures++ @@ -1010,7 +1027,7 @@ func (m *Main) ripTrack(ctx context.Context, p provider.Client, source, id, fall downloaded: embedCoverPath := opts.albumEmbedCover - if opts.forPlaylist { + if opts.forPlaylist && embedCoverPath == "" { parent := opts.albumFolder if parent == "" { parent = filepath.Dir(outPath) @@ -1020,7 +1037,7 @@ downloaded: embedCoverPath = res.EmbedPath } } - } else if embedCoverPath == "" { + } else if !opts.forPlaylist && embedCoverPath == "" { parent := opts.albumFolder if parent == "" { parent = filepath.Dir(outPath) @@ -1473,7 +1490,9 @@ func buildTagMetadata(trackMeta map[string]any, title, source, trackID string, o Artist: artist, Artists: artistNames, AlbumArtist: albumArtist, + Compilation: opts.forPlaylist, OmitDiscTags: opts.forPlaylist, + Year: opts.playlistYear, TrackNumber: trackNumber, DiscNumber: discNumber, TrackTotal: trackTotal, @@ -1646,3 +1665,23 @@ func displayArtistNames(names []string) string { return strings.Join(names[:len(names)-1], ", ") + " & " + names[len(names)-1] } } + +func extractYear(meta map[string]any) int { + date := jsonutil.FirstNonEmpty( + jsonutil.StringFromAny(meta["publish_date"]), + jsonutil.StringFromAny(meta["creation_date"]), + jsonutil.StringFromAny(meta["created_at"]), + jsonutil.StringFromAny(meta["release_date"]), + jsonutil.StringFromAny(meta["new_release_date"]), + ) + if date == "" { + return 0 + } + if t, err := time.Parse(time.RFC3339, date); err == nil { + return t.Year() + } + if t, err := time.Parse("2006-01-02", date); err == nil { + return t.Year() + } + return 0 +} diff --git a/internal/audio/tag/tagger.go b/internal/audio/tag/tagger.go index 4d83842..9c7ac1c 100644 --- a/internal/audio/tag/tagger.go +++ b/internal/audio/tag/tagger.go @@ -16,7 +16,9 @@ type Metadata struct { Artist string Artists []string AlbumArtist string + Compilation bool OmitDiscTags bool + Year int TrackNumber int DiscNumber int TrackTotal int @@ -197,6 +199,12 @@ func toTags(meta Metadata) map[string]string { if meta.DiscTotal > 0 { tags["disctotal"] = strconv.Itoa(meta.DiscTotal) } + if meta.Compilation { + tags["COMPILATION"] = "1" + } + if meta.Year > 0 { + tags["year"] = strconv.Itoa(meta.Year) + } return tags } diff --git a/internal/provider/beatport/client.go b/internal/provider/beatport/client.go index a258306..c077850 100644 --- a/internal/provider/beatport/client.go +++ b/internal/provider/beatport/client.go @@ -610,12 +610,15 @@ func (c *Client) releaseCollectionMetadata(raw map[string]any, releases []any) m func playlistMetadata(raw map[string]any, tracks []any) map[string]any { name := jsonutil.StringFromAny(raw["name"]) return map[string]any{ - "id": jsonutil.StringFromAny(raw["id"]), - "name": name, - "title": name, - "tracks_count": firstPositiveInt(jsonutil.IntFromAny(raw["track_count"]), len(tracks)), - "image": imageMap(raw["image"]), - "tracks": map[string]any{"items": tracks}, + "id": jsonutil.StringFromAny(raw["id"]), + "name": name, + "title": name, + "tracks_count": firstPositiveInt(jsonutil.IntFromAny(raw["track_count"]), len(tracks)), + "image": imageMap(raw["image"]), + "publish_date": jsonutil.StringFromAny(raw["publish_date"]), + "new_release_date": jsonutil.StringFromAny(raw["new_release_date"]), + "release_date": jsonutil.StringFromAny(raw["release_date"]), + "tracks": map[string]any{"items": tracks}, } } diff --git a/internal/provider/qobuz/client.go b/internal/provider/qobuz/client.go index 71fd070..02a2727 100644 --- a/internal/provider/qobuz/client.go +++ b/internal/provider/qobuz/client.go @@ -613,11 +613,13 @@ func (c *Client) getPlaylist(ctx context.Context, playlistID string) (map[string total, _ := intValue(resp["tracks_count"]) if total <= pageLimit { + normalizePlaylistImage(resp) return resp, nil } tracksObj, ok := mapValue(resp["tracks"]) if !ok { + normalizePlaylistImage(resp) return resp, nil } items, ok := tracksObj["items"].([]any) @@ -653,9 +655,25 @@ func (c *Client) getPlaylist(ctx context.Context, playlistID string) (map[string tracksObj["items"] = items resp["tracks"] = tracksObj + normalizePlaylistImage(resp) return resp, nil } +func normalizePlaylistImage(resp map[string]any) { + if resp["image"] != nil { + return + } + rect, ok := resp["image_rectangle"].([]any) + if !ok || len(rect) == 0 { + return + } + url, ok := rect[0].(string) + if !ok || url == "" { + return + } + resp["image"] = map[string]any{"original": url, "large": url} +} + func (c *Client) getLabel(ctx context.Context, labelID string) (map[string]any, error) { pageLimit := 500 params := url.Values{}