Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
42 changes: 26 additions & 16 deletions cmd/daemon/api_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
Expand All @@ -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"
)
Expand Down Expand Up @@ -139,15 +138,31 @@ 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"`
TrackNumber int `json:"track_number"`
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()

Expand All @@ -156,19 +171,17 @@ 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{
Uri: librespot.SpotifyIdFromGid(librespot.SpotifyIdTypeTrack, track.Gid).Uri(),
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(),
Expand All @@ -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: "",
Expand Down
2 changes: 1 addition & 1 deletion cmd/daemon/controls.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
6 changes: 5 additions & 1 deletion cmd/daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion cmd/daemon/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions cmd/daemon/product_info.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"encoding/hex"
"encoding/xml"
"strings"
)
Expand All @@ -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
}
10 changes: 10 additions & 0 deletions config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
}
},
Expand Down