fix: improve subsonic client compatibility

This commit is contained in:
2026-04-03 21:16:40 +03:00
parent a054192e45
commit 0b10dfe055
3 changed files with 166 additions and 23 deletions

View File

@@ -5,9 +5,11 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"log" "log"
"math/rand"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -98,8 +100,10 @@ func NewRouter(cfg config.Config, database *sql.DB, scanService *scanner.Service
restGet(authed, "getSong", application.subsonicSongByID) restGet(authed, "getSong", application.subsonicSongByID)
restGet(authed, "getRandomSongs", application.subsonicRandomSongs) restGet(authed, "getRandomSongs", application.subsonicRandomSongs)
restGet(authed, "getAlbumList2", application.subsonicAlbumList2) restGet(authed, "getAlbumList2", application.subsonicAlbumList2)
restGet(authed, "getSongsByGenre", application.subsonicSongsByGenre)
restGet(authed, "getMusicFolders", application.subsonicMusicFolders) restGet(authed, "getMusicFolders", application.subsonicMusicFolders)
restGet(authed, "getGenres", application.subsonicGenres) restGet(authed, "getGenres", application.subsonicGenres)
restGet(authed, "getOpenSubsonicExtensions", application.subsonicOpenSubsonicExtensions)
restGet(authed, "getPodcasts", application.subsonicPodcasts) restGet(authed, "getPodcasts", application.subsonicPodcasts)
restGet(authed, "getNewestPodcasts", application.subsonicNewestPodcasts) restGet(authed, "getNewestPodcasts", application.subsonicNewestPodcasts)
restGet(authed, "getInternetRadioStations", application.subsonicInternetRadioStations) restGet(authed, "getInternetRadioStations", application.subsonicInternetRadioStations)
@@ -469,14 +473,63 @@ func (a app) subsonicAlbumList2(w http.ResponseWriter, r *http.Request) {
size = parsed size = parsed
} }
} }
albums, err := a.library.Albums(r.Context(), size) offset := parsePositiveInt(r.URL.Query().Get("offset"))
albums, err := a.library.Albums(r.Context(), 5000)
if err != nil { if err != nil {
writeJSON(w, http.StatusInternalServerError, subsonic.ErrorResponse(0, "failed to load albums")) writeJSON(w, http.StatusInternalServerError, subsonic.ErrorResponse(0, "failed to load albums"))
return return
} }
if genre := strings.TrimSpace(r.URL.Query().Get("genre")); genre != "" {
filtered := make([]library.Album, 0, len(albums))
for _, album := range albums {
if strings.EqualFold(strings.TrimSpace(album.Genre), genre) {
filtered = append(filtered, album)
}
}
albums = filtered
}
typeName := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("type")))
switch typeName {
case "alphabeticalbyname":
sort.SliceStable(albums, func(i, j int) bool {
return strings.ToLower(albums[i].Title) < strings.ToLower(albums[j].Title)
})
case "random":
rand.Shuffle(len(albums), func(i, j int) {
albums[i], albums[j] = albums[j], albums[i]
})
default:
sort.SliceStable(albums, func(i, j int) bool {
return albums[i].Year > albums[j].Year
})
}
if offset > len(albums) {
albums = []library.Album{}
} else if offset > 0 {
albums = albums[offset:]
}
if size < len(albums) {
albums = albums[:size]
}
writeJSON(w, http.StatusOK, subsonic.AlbumList2Response(albums)) writeJSON(w, http.StatusOK, subsonic.AlbumList2Response(albums))
} }
func (a app) subsonicSongsByGenre(w http.ResponseWriter, r *http.Request) {
genre := strings.TrimSpace(r.URL.Query().Get("genre"))
if genre == "" {
writeJSON(w, http.StatusBadRequest, subsonic.ErrorResponse(10, "missing genre"))
return
}
count := parsePositiveInt(r.URL.Query().Get("count"))
offset := parsePositiveInt(r.URL.Query().Get("offset"))
tracks, err := a.library.SongsByGenre(r.Context(), genre, count, offset)
if err != nil {
writeJSON(w, http.StatusInternalServerError, subsonic.ErrorResponse(0, "failed to load songs by genre"))
return
}
writeJSON(w, http.StatusOK, subsonic.SongsByGenreResponse(tracks))
}
func (a app) subsonicMusicFolders(w http.ResponseWriter, r *http.Request) { func (a app) subsonicMusicFolders(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, subsonic.MusicFoldersResponse()) writeJSON(w, http.StatusOK, subsonic.MusicFoldersResponse())
} }
@@ -502,6 +555,10 @@ func (a app) subsonicInternetRadioStations(w http.ResponseWriter, r *http.Reques
writeJSON(w, http.StatusOK, subsonic.InternetRadioStationsResponse()) writeJSON(w, http.StatusOK, subsonic.InternetRadioStationsResponse())
} }
func (a app) subsonicOpenSubsonicExtensions(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, subsonic.OpenSubsonicExtensionsResponse())
}
func (a app) subsonicSearch3(w http.ResponseWriter, r *http.Request) { func (a app) subsonicSearch3(w http.ResponseWriter, r *http.Request) {
query := strings.TrimSpace(r.URL.Query().Get("query")) query := strings.TrimSpace(r.URL.Query().Get("query"))
if query == "" { if query == "" {

View File

@@ -36,6 +36,7 @@ type Track struct {
Title string `json:"title"` Title string `json:"title"`
ArtistName string `json:"artistName"` ArtistName string `json:"artistName"`
AlbumTitle string `json:"albumTitle"` AlbumTitle string `json:"albumTitle"`
Genre string `json:"genre"`
TrackNumber int `json:"trackNumber"` TrackNumber int `json:"trackNumber"`
DurationSecs int `json:"durationSeconds"` DurationSecs int `json:"durationSeconds"`
BitrateKbps int `json:"bitrateKbps"` BitrateKbps int `json:"bitrateKbps"`
@@ -269,7 +270,7 @@ func (s *Service) AlbumByID(ctx context.Context, id string) (AlbumDetail, error)
func (s *Service) Tracks(ctx context.Context, limit int) ([]Track, error) { func (s *Service) Tracks(ctx context.Context, limit int) ([]Track, error) {
rows, err := s.db.QueryContext( rows, err := s.db.QueryContext(
ctx, ctx,
`SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(t.track_number, 0), `SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(al.genre, ''), COALESCE(t.track_number, 0),
COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '') COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '')
FROM tracks t FROM tracks t
JOIN artists a ON a.id = t.artist_id JOIN artists a ON a.id = t.artist_id
@@ -293,6 +294,7 @@ func (s *Service) Tracks(ctx context.Context, limit int) ([]Track, error) {
&track.Title, &track.Title,
&track.ArtistName, &track.ArtistName,
&track.AlbumTitle, &track.AlbumTitle,
&track.Genre,
&track.TrackNumber, &track.TrackNumber,
&track.DurationSecs, &track.DurationSecs,
&track.BitrateKbps, &track.BitrateKbps,
@@ -313,7 +315,7 @@ func (s *Service) TrackByID(ctx context.Context, id string) (Track, error) {
err := s.db.QueryRowContext( err := s.db.QueryRowContext(
ctx, ctx,
`SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(t.track_number, 0), `SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(al.genre, ''), COALESCE(t.track_number, 0),
COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '') COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '')
FROM tracks t FROM tracks t
JOIN artists a ON a.id = t.artist_id JOIN artists a ON a.id = t.artist_id
@@ -327,6 +329,7 @@ func (s *Service) TrackByID(ctx context.Context, id string) (Track, error) {
&track.Title, &track.Title,
&track.ArtistName, &track.ArtistName,
&track.AlbumTitle, &track.AlbumTitle,
&track.Genre,
&track.TrackNumber, &track.TrackNumber,
&track.DurationSecs, &track.DurationSecs,
&track.BitrateKbps, &track.BitrateKbps,
@@ -415,6 +418,36 @@ func (s *Service) Genres(ctx context.Context) ([]GenreSummary, error) {
return genres, rows.Err() return genres, rows.Err()
} }
func (s *Service) SongsByGenre(ctx context.Context, genre string, count, offset int) ([]Track, error) {
if count <= 0 {
count = 50
}
if offset < 0 {
offset = 0
}
rows, err := s.db.QueryContext(
ctx,
`SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(al.genre, ''), COALESCE(t.track_number, 0),
COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '')
FROM tracks t
JOIN artists a ON a.id = t.artist_id
JOIN albums al ON al.id = t.album_id
WHERE LOWER(TRIM(COALESCE(al.genre, ''))) = LOWER(TRIM(?))
ORDER BY a.name ASC, al.year DESC, al.title ASC, t.disc_number ASC, t.track_number ASC
LIMIT ? OFFSET ?`,
genre,
count,
offset,
)
if err != nil {
return nil, fmt.Errorf("query songs by genre: %w", err)
}
defer rows.Close()
return scanTracks(rows)
}
func (s *Service) Star(ctx context.Context, userID string, trackIDs, albumIDs, artistIDs []string) error { func (s *Service) Star(ctx context.Context, userID string, trackIDs, albumIDs, artistIDs []string) error {
return s.updateFavorites(ctx, userID, trackIDs, albumIDs, artistIDs, true) return s.updateFavorites(ctx, userID, trackIDs, albumIDs, artistIDs, true)
} }
@@ -463,7 +496,7 @@ func (s *Service) RecentTracks(ctx context.Context, userID string, limit int) ([
if userID != "" { if userID != "" {
rows, err := s.db.QueryContext( rows, err := s.db.QueryContext(
ctx, ctx,
`SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(t.track_number, 0), `SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(al.genre, ''), COALESCE(t.track_number, 0),
COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '') COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '')
FROM tracks t FROM tracks t
JOIN artists a ON a.id = t.artist_id JOIN artists a ON a.id = t.artist_id
@@ -488,7 +521,7 @@ func (s *Service) RecentTracks(ctx context.Context, userID string, limit int) ([
rows, err := s.db.QueryContext( rows, err := s.db.QueryContext(
ctx, ctx,
`SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(t.track_number, 0), `SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(al.genre, ''), COALESCE(t.track_number, 0),
COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '') COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '')
FROM tracks t FROM tracks t
JOIN artists a ON a.id = t.artist_id JOIN artists a ON a.id = t.artist_id
@@ -617,7 +650,7 @@ func (s *Service) albumsByArtistID(ctx context.Context, artistID string) ([]Albu
func (s *Service) tracksByAlbumID(ctx context.Context, albumID string) ([]Track, error) { func (s *Service) tracksByAlbumID(ctx context.Context, albumID string) ([]Track, error) {
rows, err := s.db.QueryContext( rows, err := s.db.QueryContext(
ctx, ctx,
`SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(t.track_number, 0), `SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(al.genre, ''), COALESCE(t.track_number, 0),
COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '') COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '')
FROM tracks t FROM tracks t
JOIN artists a ON a.id = t.artist_id JOIN artists a ON a.id = t.artist_id
@@ -697,8 +730,8 @@ func (s *Service) searchAlbums(ctx context.Context, pattern string, limit int) (
func (s *Service) searchTracks(ctx context.Context, pattern string, limit int) ([]Track, error) { func (s *Service) searchTracks(ctx context.Context, pattern string, limit int) ([]Track, error) {
rows, err := s.db.QueryContext( rows, err := s.db.QueryContext(
ctx, ctx,
`SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(t.track_number, 0), `SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(al.genre, ''), COALESCE(t.track_number, 0),
COALESCE(t.duration_seconds, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '') COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '')
FROM tracks t FROM tracks t
JOIN artists a ON a.id = t.artist_id JOIN artists a ON a.id = t.artist_id
JOIN albums al ON al.id = t.album_id JOIN albums al ON al.id = t.album_id
@@ -817,7 +850,7 @@ func (s *Service) starredAlbums(ctx context.Context, userID string) ([]Album, er
func (s *Service) starredTracks(ctx context.Context, userID string) ([]Track, error) { func (s *Service) starredTracks(ctx context.Context, userID string) ([]Track, error) {
rows, err := s.db.QueryContext( rows, err := s.db.QueryContext(
ctx, ctx,
`SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(t.track_number, 0), `SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(al.genre, ''), COALESCE(t.track_number, 0),
COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '') COALESCE(t.duration_seconds, 0), COALESCE(t.bitrate_kbps, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '')
FROM favorites f FROM favorites f
JOIN tracks t ON t.id = f.entity_id JOIN tracks t ON t.id = f.entity_id
@@ -846,6 +879,7 @@ func scanTracks(rows *sql.Rows) ([]Track, error) {
&track.Title, &track.Title,
&track.ArtistName, &track.ArtistName,
&track.AlbumTitle, &track.AlbumTitle,
&track.Genre,
&track.TrackNumber, &track.TrackNumber,
&track.DurationSecs, &track.DurationSecs,
&track.BitrateKbps, &track.BitrateKbps,

View File

@@ -25,6 +25,7 @@ type Response struct {
Artist *ArtistFull `json:"artist,omitempty"` Artist *ArtistFull `json:"artist,omitempty"`
Album *AlbumFull `json:"album,omitempty"` Album *AlbumFull `json:"album,omitempty"`
AlbumList2 *AlbumList2 `json:"albumList2,omitempty"` AlbumList2 *AlbumList2 `json:"albumList2,omitempty"`
SongsByGenre *SongsByGenre `json:"songsByGenre,omitempty"`
Song *SongFull `json:"song,omitempty"` Song *SongFull `json:"song,omitempty"`
RandomSong []SongRef `json:"randomSongs,omitempty"` RandomSong []SongRef `json:"randomSongs,omitempty"`
SearchResult3 *SearchResult3 `json:"searchResult3,omitempty"` SearchResult3 *SearchResult3 `json:"searchResult3,omitempty"`
@@ -36,6 +37,7 @@ type Response struct {
Podcasts *Podcasts `json:"podcasts,omitempty"` Podcasts *Podcasts `json:"podcasts,omitempty"`
NewestPods *NewestPods `json:"newestPodcasts,omitempty"` NewestPods *NewestPods `json:"newestPodcasts,omitempty"`
RadioStations *RadioStations `json:"internetRadioStations,omitempty"` RadioStations *RadioStations `json:"internetRadioStations,omitempty"`
Extensions *Extensions `json:"openSubsonicExtensions,omitempty"`
ScanStatus *ScanStatus `json:"scanStatus,omitempty"` ScanStatus *ScanStatus `json:"scanStatus,omitempty"`
Error *ErrorRef `json:"error,omitempty"` Error *ErrorRef `json:"error,omitempty"`
} }
@@ -74,6 +76,7 @@ type ArtistFull struct {
type AlbumRef struct { type AlbumRef struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name,omitempty"`
Title string `json:"title"` Title string `json:"title"`
Artist string `json:"artist"` Artist string `json:"artist"`
ArtistID string `json:"artistId"` ArtistID string `json:"artistId"`
@@ -96,6 +99,10 @@ type AlbumList2 struct {
Album []AlbumRef `json:"album,omitempty"` Album []AlbumRef `json:"album,omitempty"`
} }
type SongsByGenre struct {
Song []SongRef `json:"song,omitempty"`
}
type SongFull struct { type SongFull struct {
ID string `json:"id"` ID string `json:"id"`
Title string `json:"title"` Title string `json:"title"`
@@ -188,6 +195,15 @@ type RadioStations struct {
InternetRadioStation []any `json:"internetRadioStation,omitempty"` InternetRadioStation []any `json:"internetRadioStation,omitempty"`
} }
type Extensions struct {
Extension []Extension `json:"extension,omitempty"`
}
type Extension struct {
Name string `json:"name"`
Versions string `json:"versions"`
}
type ErrorRef struct { type ErrorRef struct {
Code int `json:"code"` Code int `json:"code"`
Message string `json:"message"` Message string `json:"message"`
@@ -248,7 +264,7 @@ func RandomSongsResponse(tracks []library.Track) Envelope {
Artist: track.ArtistName, Artist: track.ArtistName,
AlbumID: track.AlbumID, AlbumID: track.AlbumID,
ArtistID: track.ArtistID, ArtistID: track.ArtistID,
CoverArt: track.CoverArtID, CoverArt: track.AlbumID,
}) })
} }
return response return response
@@ -265,12 +281,13 @@ func ArtistResponse(artist library.ArtistDetail) Envelope {
for _, album := range artist.Albums { for _, album := range artist.Albums {
item.Albums = append(item.Albums, AlbumRef{ item.Albums = append(item.Albums, AlbumRef{
ID: album.ID, ID: album.ID,
Name: album.Title,
Title: album.Title, Title: album.Title,
Artist: album.ArtistName, Artist: album.ArtistName,
ArtistID: album.ArtistID, ArtistID: album.ArtistID,
Year: album.Year, Year: album.Year,
Genre: album.Genre, Genre: album.Genre,
CoverArt: album.CoverArtID, CoverArt: album.ID,
}) })
} }
response.SubsonicResponse.Artist = item response.SubsonicResponse.Artist = item
@@ -289,12 +306,13 @@ func Search3Response(results library.SearchResults) Envelope {
for _, album := range results.Albums { for _, album := range results.Albums {
payload.Album = append(payload.Album, AlbumRef{ payload.Album = append(payload.Album, AlbumRef{
ID: album.ID, ID: album.ID,
Name: album.Title,
Title: album.Title, Title: album.Title,
Artist: album.ArtistName, Artist: album.ArtistName,
ArtistID: album.ArtistID, ArtistID: album.ArtistID,
Year: album.Year, Year: album.Year,
Genre: album.Genre, Genre: album.Genre,
CoverArt: album.CoverArtID, CoverArt: album.ID,
}) })
} }
for _, track := range results.Tracks { for _, track := range results.Tracks {
@@ -305,7 +323,7 @@ func Search3Response(results library.SearchResults) Envelope {
Artist: track.ArtistName, Artist: track.ArtistName,
AlbumID: track.AlbumID, AlbumID: track.AlbumID,
ArtistID: track.ArtistID, ArtistID: track.ArtistID,
CoverArt: track.CoverArtID, CoverArt: track.AlbumID,
}) })
} }
response.SubsonicResponse.SearchResult3 = payload response.SubsonicResponse.SearchResult3 = payload
@@ -324,12 +342,13 @@ func Starred2Response(results library.StarredResults) Envelope {
for _, album := range results.Albums { for _, album := range results.Albums {
payload.Album = append(payload.Album, AlbumRef{ payload.Album = append(payload.Album, AlbumRef{
ID: album.ID, ID: album.ID,
Name: album.Title,
Title: album.Title, Title: album.Title,
Artist: album.ArtistName, Artist: album.ArtistName,
ArtistID: album.ArtistID, ArtistID: album.ArtistID,
Year: album.Year, Year: album.Year,
Genre: album.Genre, Genre: album.Genre,
CoverArt: album.CoverArtID, CoverArt: album.ID,
}) })
} }
for _, track := range results.Tracks { for _, track := range results.Tracks {
@@ -340,7 +359,7 @@ func Starred2Response(results library.StarredResults) Envelope {
Artist: track.ArtistName, Artist: track.ArtistName,
AlbumID: track.AlbumID, AlbumID: track.AlbumID,
ArtistID: track.ArtistID, ArtistID: track.ArtistID,
CoverArt: track.CoverArtID, CoverArt: track.AlbumID,
}) })
} }
response.SubsonicResponse.Starred2 = payload response.SubsonicResponse.Starred2 = payload
@@ -387,7 +406,7 @@ func PlaylistResponse(owner string, detail playlist.Detail) Envelope {
Artist: track.ArtistName, Artist: track.ArtistName,
AlbumID: track.AlbumID, AlbumID: track.AlbumID,
ArtistID: track.ArtistID, ArtistID: track.ArtistID,
CoverArt: track.CoverArtID, CoverArt: track.AlbumID,
}) })
} }
response.SubsonicResponse.Playlist = item response.SubsonicResponse.Playlist = item
@@ -402,14 +421,17 @@ func AlbumResponse(album library.AlbumDetail) Envelope {
Artist: album.ArtistName, Artist: album.ArtistName,
ArtistID: album.ArtistID, ArtistID: album.ArtistID,
Year: album.Year, Year: album.Year,
CoverArt: album.CoverArtID, CoverArt: album.ID,
} }
for _, track := range album.Tracks { for _, track := range album.Tracks {
item.Song = append(item.Song, SongRef{ item.Song = append(item.Song, SongRef{
ID: track.ID, ID: track.ID,
Title: track.Title, Title: track.Title,
Album: track.AlbumTitle, Album: track.AlbumTitle,
Artist: track.ArtistName, Artist: track.ArtistName,
AlbumID: track.AlbumID,
ArtistID: track.ArtistID,
CoverArt: track.AlbumID,
}) })
} }
response.SubsonicResponse.Album = item response.SubsonicResponse.Album = item
@@ -422,18 +444,37 @@ func AlbumList2Response(albums []library.Album) Envelope {
for _, album := range albums { for _, album := range albums {
payload.Album = append(payload.Album, AlbumRef{ payload.Album = append(payload.Album, AlbumRef{
ID: album.ID, ID: album.ID,
Name: album.Title,
Title: album.Title, Title: album.Title,
Artist: album.ArtistName, Artist: album.ArtistName,
ArtistID: album.ArtistID, ArtistID: album.ArtistID,
Year: album.Year, Year: album.Year,
Genre: album.Genre, Genre: album.Genre,
CoverArt: album.CoverArtID, CoverArt: album.ID,
}) })
} }
response.SubsonicResponse.AlbumList2 = payload response.SubsonicResponse.AlbumList2 = payload
return response return response
} }
func SongsByGenreResponse(tracks []library.Track) Envelope {
response := PingResponse()
payload := &SongsByGenre{}
for _, track := range tracks {
payload.Song = append(payload.Song, SongRef{
ID: track.ID,
Title: track.Title,
Album: track.AlbumTitle,
Artist: track.ArtistName,
AlbumID: track.AlbumID,
ArtistID: track.ArtistID,
CoverArt: track.AlbumID,
})
}
response.SubsonicResponse.SongsByGenre = payload
return response
}
func SongResponse(track library.Track) Envelope { func SongResponse(track library.Track) Envelope {
response := PingResponse() response := PingResponse()
response.SubsonicResponse.Song = &SongFull{ response.SubsonicResponse.Song = &SongFull{
@@ -445,7 +486,7 @@ func SongResponse(track library.Track) Envelope {
ArtistID: track.ArtistID, ArtistID: track.ArtistID,
Track: track.TrackNumber, Track: track.TrackNumber,
Duration: track.DurationSecs, Duration: track.DurationSecs,
CoverArt: track.CoverArtID, CoverArt: track.AlbumID,
} }
return response return response
} }
@@ -503,6 +544,17 @@ func InternetRadioStationsResponse() Envelope {
return response return response
} }
func OpenSubsonicExtensionsResponse() Envelope {
response := PingResponse()
response.SubsonicResponse.Extensions = &Extensions{
Extension: []Extension{
{Name: "formPost", Versions: "1"},
{Name: "apiKeyAuthentication", Versions: "1"},
},
}
return response
}
func ErrorResponse(code int, message string) Envelope { func ErrorResponse(code int, message string) Envelope {
response := PingResponse() response := PingResponse()
response.SubsonicResponse.Status = "failed" response.SubsonicResponse.Status = "failed"