Skip to content

Commit

Permalink
feat(subsonic): implement getSimilarSongs.view
Browse files Browse the repository at this point in the history
closes #195
  • Loading branch information
xavier authored and sentriz committed Feb 10, 2022
1 parent 39b3ae5 commit e1cfed7
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 0 deletions.
63 changes: 63 additions & 0 deletions server/ctrlsubsonic/handlers_by_tags.go
Expand Up @@ -449,3 +449,66 @@ func (c *Controller) ServeGetTopSongs(r *http.Request) *spec.Response {
}
return sub
}

func (c *Controller) ServeGetSimilarSongs(r *http.Request) *spec.Response {
params := r.Context().Value(CtxParams).(params.Params)
count := params.GetOrInt("count", 10)
id, err := params.GetID("id")
if err != nil || id.Type != specid.Track {
return spec.NewError(10, "please provide an track `id` parameter")
}
apiKey, _ := c.DB.GetSetting("lastfm_api_key")
if apiKey == "" {
return spec.NewResponse()
}

var track db.Track
err = c.DB.
Preload("Artist").
Preload("Album").
Where("id=?", id.Value).
First(&track).
Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return spec.NewError(10, "couldn't find a track with that id")
}

similarTracks, err := lastfm.TrackGetSimilarTracks(apiKey, track.Artist.Name, track.TagTitle)
if err != nil {
return spec.NewError(0, "fetching track similar tracks: %v", err)
}
if len(similarTracks.Tracks) == 0 {
return spec.NewError(70, "no similar songs found for track: %v", track.TagTitle)
}

similarTrackNames := make([]string, len(similarTracks.Tracks))
for i, t := range similarTracks.Tracks {
similarTrackNames[i] = t.Name
}

var tracks []*db.Track
err = c.DB.
Preload("Artist").
Preload("Album").
Select("tracks.*").
Where("tracks.tag_title IN (?)", similarTrackNames).
Order(gorm.Expr("random()")).
Limit(count).
Find(&tracks).
Error
if err != nil {
return spec.NewError(0, "error finding tracks: %v", err)
}
if len(tracks) == 0 {
return spec.NewError(70, "no similar song could be match with collection in database: %v", track.TagTitle)
}

sub := spec.NewResponse()
sub.SimilarSongs = &spec.SimilarSongs{
Tracks: make([]*spec.TrackChild, len(tracks)),
}
for i, track := range tracks {
sub.SimilarSongs.Tracks[i] = spec.NewTrackByTags(track, track.Album)
}
return sub
}
5 changes: 5 additions & 0 deletions server/ctrlsubsonic/spec/spec.go
Expand Up @@ -48,6 +48,7 @@ type Response struct {
Starred *Starred `xml:"starred" json:"starred,omitempty"`
StarredTwo *StarredTwo `xml:"starred2" json:"starred2,omitempty"`
TopSongs *TopSongs `xml:"topSongs" json:"topSongs,omitempty"`
SimilarSongs *SimilarSongs `xml:"similarSongs" json:"similarSongs,omitempty"`
}

func NewResponse() *Response {
Expand Down Expand Up @@ -357,3 +358,7 @@ type StarredTwo struct {
type TopSongs struct {
Tracks []*TrackChild `xml:"song,omitempty" json:"song,omitempty"`
}

type SimilarSongs struct {
Tracks []*TrackChild `xml:"song,omitempty" json:"song,omitempty"`
}
20 changes: 20 additions & 0 deletions server/scrobble/lastfm/lastfm.go
Expand Up @@ -31,6 +31,7 @@ type LastFM struct {
Error Error `xml:"error"`
Artist Artist `xml:"artist"`
TopTracks TopTracks `xml:"toptracks"`
SimilarTracks SimilarTracks `xml:"similartracks"`
}

type Session struct {
Expand Down Expand Up @@ -84,6 +85,13 @@ type TopTracks struct {
Tracks []Track `xml:"track"`
}

type SimilarTracks struct {
XMLName xml.Name `xml:"similartracks"`
Artist string `xml:"artist,attr"`
Track string `xml:"track,attr"`
Tracks []Track `xml:"track"`
}

type Track struct {
Rank int `xml:"rank,attr"`
Tracks []Track `xml:"track"`
Expand Down Expand Up @@ -158,6 +166,18 @@ func ArtistGetTopTracks(apiKey, artistName string) (TopTracks, error) {
return resp.TopTracks, nil
}

func TrackGetSimilarTracks(apiKey string, artistName, trackName string) (SimilarTracks, error) {
params := url.Values{}
params.Add("method", "track.getSimilar")
params.Add("api_key", apiKey)
params.Add("track", trackName)
params.Add("artist", artistName)
resp, err := makeRequest("GET", params)
if err != nil {
return SimilarTracks{}, fmt.Errorf("making track GET: %w", err)
}
return resp.SimilarTracks, nil
}
func GetSession(apiKey, secret, token string) (string, error) {
params := url.Values{}
params.Add("method", "auth.getSession")
Expand Down
1 change: 1 addition & 0 deletions server/server.go
Expand Up @@ -218,6 +218,7 @@ func setupSubsonic(r *mux.Router, ctrl *ctrlsubsonic.Controller) {
r.Handle("/createBookmark{_:(?:\\.view)?}", ctrl.H(ctrl.ServeCreateBookmark))
r.Handle("/deleteBookmark{_:(?:\\.view)?}", ctrl.H(ctrl.ServeDeleteBookmark))
r.Handle("/getTopSongs{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetTopSongs))
r.Handle("/getSimilarSongs{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetSimilarSongs))

// raw
r.Handle("/download{_:(?:\\.view)?}", ctrl.HR(ctrl.ServeDownload))
Expand Down

0 comments on commit e1cfed7

Please sign in to comment.