diff --git a/README.md b/README.md index dc274dc..dcdb715 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ server: allow_origin: '' # Value for the Access-Control-Allow-Origin header cert_file: '' # Path to certificate file for TLS key_file: '' # Path to key file for TLS + image_size: 'default' # Album art image size (default, small, large, xlarge) ``` For detailed API documentation see [here](/API.md). diff --git a/cmd/daemon/api_server.go b/cmd/daemon/api_server.go index c4e775e..48dd87f 100644 --- a/cmd/daemon/api_server.go +++ b/cmd/daemon/api_server.go @@ -2,7 +2,6 @@ package main import ( "context" - "encoding/hex" "encoding/json" "errors" "fmt" @@ -14,9 +13,9 @@ import ( "sync" "time" - "github.com/rs/cors" - librespot "github.com/devgianlu/go-librespot" + metadatapb "github.com/devgianlu/go-librespot/proto/spotify/metadata" + "github.com/rs/cors" "nhooyr.io/websocket" "nhooyr.io/websocket/wsjson" ) @@ -139,7 +138,7 @@ type ApiResponseStatusTrack struct { Name string `json:"name"` ArtistNames []string `json:"artist_names"` AlbumName string `json:"album_name"` - AlbumCoverUrl string `json:"album_cover_url"` + AlbumCoverUrl *string `json:"album_cover_url"` Position int64 `json:"position"` Duration int `json:"duration"` ReleaseDate string `json:"release_date"` @@ -147,7 +146,23 @@ type ApiResponseStatusTrack struct { DiscNumber int `json:"disc_number"` } -func NewApiResponseStatusTrack(media *librespot.Media, prodInfo *ProductInfo, position int64) *ApiResponseStatusTrack { +func getBestImageIdForSize(images []*metadatapb.Image, size string) []byte { + if len(images) == 0 { + return nil + } + + imageSize := metadatapb.Image_Size(metadatapb.Image_Size_value[strings.ToUpper(size)]) + + for _, img := range images { + if img.Size != nil && *img.Size == imageSize { + return img.FileId + } + } + + return images[0].FileId +} + +func (p *AppPlayer) newApiResponseStatusTrack(media *librespot.Media, position int64) *ApiResponseStatusTrack { if media.IsTrack() { track := media.Track() @@ -156,11 +171,9 @@ func NewApiResponseStatusTrack(media *librespot.Media, prodInfo *ProductInfo, po artists = append(artists, *a.Name) } - var albumCoverId string - if len(track.Album.Cover) > 0 { - albumCoverId = hex.EncodeToString(track.Album.Cover[0].FileId) - } else if track.Album.CoverGroup != nil && len(track.Album.CoverGroup.Image) > 0 { - albumCoverId = hex.EncodeToString(track.Album.CoverGroup.Image[0].FileId) + albumCoverId := getBestImageIdForSize(track.Album.Cover, p.app.cfg.Server.ImageSize) + if albumCoverId == nil && track.Album.CoverGroup != nil { + albumCoverId = getBestImageIdForSize(track.Album.CoverGroup.Image, p.app.cfg.Server.ImageSize) } return &ApiResponseStatusTrack{ @@ -168,7 +181,7 @@ func NewApiResponseStatusTrack(media *librespot.Media, prodInfo *ProductInfo, po Name: *track.Name, ArtistNames: artists, AlbumName: *track.Album.Name, - AlbumCoverUrl: prodInfo.ImageUrl(albumCoverId), + AlbumCoverUrl: p.prodInfo.ImageUrl(albumCoverId), Position: position, Duration: int(*track.Duration), ReleaseDate: track.Album.Date.String(), @@ -178,17 +191,14 @@ func NewApiResponseStatusTrack(media *librespot.Media, prodInfo *ProductInfo, po } else { episode := media.Episode() - var albumCoverId string - if len(episode.CoverImage.Image) > 0 { - albumCoverId = hex.EncodeToString(episode.CoverImage.Image[0].FileId) - } + albumCoverId := getBestImageIdForSize(episode.CoverImage.Image, p.app.cfg.Server.ImageSize) return &ApiResponseStatusTrack{ Uri: librespot.SpotifyIdFromGid(librespot.SpotifyIdTypeEpisode, episode.Gid).Uri(), Name: *episode.Name, ArtistNames: []string{*episode.Show.Name}, AlbumName: *episode.Show.Name, - AlbumCoverUrl: prodInfo.ImageUrl(albumCoverId), + AlbumCoverUrl: p.prodInfo.ImageUrl(albumCoverId), Position: position, Duration: int(*episode.Duration), ReleaseDate: "", diff --git a/cmd/daemon/controls.go b/cmd/daemon/controls.go index c239912..ed37169 100644 --- a/cmd/daemon/controls.go +++ b/cmd/daemon/controls.go @@ -358,7 +358,7 @@ func (p *AppPlayer) loadCurrentTrack(ctx context.Context, paused, drop bool) err p.app.server.Emit(&ApiEvent{ Type: ApiEventTypeMetadata, - Data: ApiEventDataMetadata(*NewApiResponseStatusTrack(p.primaryStream.Media, p.prodInfo, trackPosition)), + Data: ApiEventDataMetadata(*p.newApiResponseStatusTrack(p.primaryStream.Media, trackPosition)), }) return nil } diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index cb30246..214accf 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -421,6 +421,8 @@ type Config struct { AllowOrigin string `koanf:"allow_origin"` CertFile string `koanf:"cert_file"` KeyFile string `koanf:"key_file"` + + ImageSize string `koanf:"image_size"` } `koanf:"server"` Credentials struct { Type string `koanf:"type"` @@ -489,7 +491,9 @@ func loadConfig(cfg *Config) error { "initial_volume": 100, "credentials.type": "zeroconf", - "server.address": "localhost", + + "server.address": "localhost", + "server.image_size": "default", }, "."), nil) // load file configuration (if available) diff --git a/cmd/daemon/player.go b/cmd/daemon/player.go index 77dd417..4839e4e 100644 --- a/cmd/daemon/player.go +++ b/cmd/daemon/player.go @@ -435,7 +435,7 @@ func (p *AppPlayer) handleApiRequest(ctx context.Context, req ApiRequest) (any, } if p.primaryStream != nil && p.prodInfo != nil { - resp.Track = NewApiResponseStatusTrack(p.primaryStream.Media, p.prodInfo, p.state.trackPosition()) + resp.Track = p.newApiResponseStatusTrack(p.primaryStream.Media, p.state.trackPosition()) } return resp, nil diff --git a/cmd/daemon/product_info.go b/cmd/daemon/product_info.go index adfef6f..bceeaa8 100644 --- a/cmd/daemon/product_info.go +++ b/cmd/daemon/product_info.go @@ -1,6 +1,7 @@ package main import ( + "encoding/hex" "encoding/xml" "strings" ) @@ -15,6 +16,14 @@ type ProductInfo struct { } `xml:"product"` } -func (pi ProductInfo) ImageUrl(fileId string) string { - return strings.Replace(pi.Products[0].ImageUrl, "{file_id}", strings.ToLower(fileId), 1) +func (pi ProductInfo) ImageUrl(fileId []byte) *string { + if len(pi.Products) == 0 || pi.Products[0].ImageUrl == "" { + return nil + } else if len(fileId) == 0 { + return nil + } + + fileIdHex := strings.ToLower(hex.EncodeToString(fileId)) + val := strings.Replace(pi.Products[0].ImageUrl, "{file_id}", fileIdHex, 1) + return &val } diff --git a/config_schema.json b/config_schema.json index 02f6464..1be85af 100644 --- a/config_schema.json +++ b/config_schema.json @@ -147,6 +147,16 @@ "type": "string", "description": "File path of the private key file to use for TLS", "default": "" + }, + "image_size": { + "type": "string", + "description": "The preferred size of album cover images served by the API server", + "enum": [ + "default", + "small", + "large", + "xlarge" + ] } } },