fix: improve subsonic client compatibility
This commit is contained in:
@@ -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 == "" {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user