diff --git a/internal/app/app.go b/internal/app/app.go index 5c4ebaa..494533d 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -39,6 +39,7 @@ type Main struct { type ripTrackOptions struct { albumFolder string albumEmbedCover string + albumArtist string index int total int albumDiscTotal int @@ -498,7 +499,7 @@ func (m *Main) ripAlbum(ctx context.Context, p provider.Client, source, albumID if !m.Config.Session.Downloads.Concurrency || m.Config.Session.Downloads.MaxConnections == 1 { for i, trackID := range trackIDs { - opts := ripTrackOptions{albumFolder: folder, albumEmbedCover: artRes.EmbedPath, index: i + 1, total: total, albumDiscTotal: discTotal} + opts := ripTrackOptions{albumFolder: folder, albumEmbedCover: artRes.EmbedPath, albumArtist: albumArtist, index: i + 1, total: total, albumDiscTotal: discTotal} if err := m.ripTrack(ctx, p, source, trackID, "", opts); err != nil { failures++ m.logf("track failed: id=%s reason=%v\n", trackID, err) @@ -523,7 +524,7 @@ func (m *Main) ripAlbum(ctx context.Context, p provider.Client, source, albumID go func(idx int, tid string) { defer wg.Done() defer func() { <-sem }() - opts := ripTrackOptions{albumFolder: folder, albumEmbedCover: artRes.EmbedPath, index: idx, total: total, albumDiscTotal: discTotal} + opts := ripTrackOptions{albumFolder: folder, albumEmbedCover: artRes.EmbedPath, albumArtist: albumArtist, index: idx, total: total, albumDiscTotal: discTotal} if err := m.ripTrack(ctx, p, source, tid, "", opts); err != nil { errCh <- err } @@ -836,6 +837,15 @@ func (m *Main) trackOutputPath(source, id, title, ext string, trackMeta map[stri base = albumFolder if m.Config.Session.Downloads.DiscSubdirectories && albumDiscTotal > 1 { discNumber := intFromAny(trackMeta["media_number"]) + if discNumber == 0 { + discNumber = intFromAny(trackMeta["volumeNumber"]) + } + if discNumber == 0 { + discNumber = intFromAny(trackMeta["disk_number"]) + } + if discNumber == 0 { + discNumber = 1 + } if discNumber > 0 { base = filepath.Join(base, "Disc "+strconv.Itoa(discNumber)) } @@ -1012,6 +1022,9 @@ func buildTagMetadata(trackMeta map[string]any, title, source, trackID string, o if albumArtist == "" { albumArtist = artist } + if strings.TrimSpace(opts.albumArtist) != "" { + albumArtist = strings.TrimSpace(opts.albumArtist) + } trackNumber := intFromAny(trackMeta["track_number"]) if trackNumber == 0 { trackNumber = intFromAny(trackMeta["trackNumber"]) @@ -1020,6 +1033,9 @@ func buildTagMetadata(trackMeta map[string]any, title, source, trackID string, o if discNumber == 0 { discNumber = intFromAny(trackMeta["volumeNumber"]) } + if discNumber == 0 { + discNumber = intFromAny(trackMeta["disk_number"]) + } date := stringFromAny(trackMeta["release_date_original"]) if date == "" { date = stringFromAny(trackMeta["release_date"]) @@ -1046,9 +1062,15 @@ func buildTagMetadata(trackMeta map[string]any, title, source, trackID string, o if discTotal == 0 { discTotal = intFromAny(trackMeta["numberOfVolumes"]) } + if discTotal == 0 && opts.albumDiscTotal > 0 { + discTotal = opts.albumDiscTotal + } if opts.forPlaylist { discTotal = 1 } + if !opts.forPlaylist && discNumber == 0 { + discNumber = 1 + } genre := nestedString(trackMeta, "genre", "name") if genre == "" { diff --git a/internal/app/app_test.go b/internal/app/app_test.go index e926ebb..fb0aada 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -6,6 +6,7 @@ import ( "net/http/httptest" "os" "path/filepath" + "strings" "testing" "streamrip-go/internal/audio/tag" @@ -368,6 +369,51 @@ func TestResolveAllFailedReturnsError(t *testing.T) { } } +func TestBuildTagMetadataUsesAlbumArtistOverride(t *testing.T) { + meta := map[string]any{ + "title": "One Step Too Far", + "track_number": float64(15), + "performer": map[string]any{"name": "Faithless"}, + "artist": map[string]any{"name": "Faithless"}, + "album": map[string]any{ + "id": "23324600", + "title": "Greatest Hits (Deluxe)", + "artist": map[string]any{"name": "Faithless"}, + }, + } + tags := buildTagMetadata(meta, "One Step Too Far", "tidal", "23324615", ripTrackOptions{albumArtist: "Dido", albumDiscTotal: 2}) + if tags.AlbumArtist != "Dido" { + t.Fatalf("album artist = %q, want Dido", tags.AlbumArtist) + } + if tags.DiscNumber != 1 { + t.Fatalf("disc number = %d, want 1", tags.DiscNumber) + } + if tags.DiscTotal != 2 { + t.Fatalf("disc total = %d, want 2", tags.DiscTotal) + } +} + +func TestTrackOutputPathFallsBackToDisc1(t *testing.T) { + tmp := t.TempDir() + d := config.DefaultConfigData() + d.Downloads.Folder = tmp + d.Downloads.DiscSubdirectories = true + d.Downloads.SourceSubdirectories = false + cfg := &config.Config{File: d, Session: d} + m := &Main{Config: cfg} + + meta := map[string]any{ + "title": "Song", + "track_number": float64(1), + "performer": map[string]any{"name": "Dido"}, + "album": map[string]any{"id": "a", "title": "Greatest Hits", "artist": map[string]any{"name": "Dido"}}, + } + path := m.trackOutputPath("tidal", "1", "Song", "flac", meta, filepath.Join(tmp, "Album"), 2) + if !strings.Contains(path, string(filepath.Separator)+"Disc 1"+string(filepath.Separator)) { + t.Fatalf("expected Disc 1 subdir in path, got %q", path) + } +} + func TestPlaylistRipPipeline(t *testing.T) { tmp := t.TempDir()