385 lines
10 KiB
Go
385 lines
10 KiB
Go
package subsonic
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/benya/temporserv/internal/library"
|
|
"github.com/benya/temporserv/internal/playlist"
|
|
"github.com/benya/temporserv/internal/scanner"
|
|
)
|
|
|
|
type Envelope struct {
|
|
SubsonicResponse Response `json:"subsonic-response"`
|
|
}
|
|
|
|
type Response struct {
|
|
Status string `json:"status"`
|
|
Version string `json:"version"`
|
|
Type string `json:"type"`
|
|
Server string `json:"serverVersion"`
|
|
OpenAPI bool `json:"openSubsonic"`
|
|
Artists []ArtistRef `json:"artists,omitempty"`
|
|
Artist *ArtistFull `json:"artist,omitempty"`
|
|
Album *AlbumFull `json:"album,omitempty"`
|
|
Song *SongFull `json:"song,omitempty"`
|
|
RandomSong []SongRef `json:"randomSongs,omitempty"`
|
|
SearchResult3 *SearchResult3 `json:"searchResult3,omitempty"`
|
|
Starred2 *Starred2 `json:"starred2,omitempty"`
|
|
Playlists *Playlists `json:"playlists,omitempty"`
|
|
Playlist *Playlist `json:"playlist,omitempty"`
|
|
ScanStatus *ScanStatus `json:"scanStatus,omitempty"`
|
|
Error *ErrorRef `json:"error,omitempty"`
|
|
}
|
|
|
|
type ArtistRef struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type SongRef struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Album string `json:"album"`
|
|
Artist string `json:"artist"`
|
|
AlbumID string `json:"albumId,omitempty"`
|
|
ArtistID string `json:"artistId,omitempty"`
|
|
CoverArt string `json:"coverArt,omitempty"`
|
|
}
|
|
|
|
type ArtistFull struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
CoverArt string `json:"coverArt,omitempty"`
|
|
AlbumCount int `json:"albumCount,omitempty"`
|
|
Albums []AlbumRef `json:"album,omitempty"`
|
|
}
|
|
|
|
type AlbumRef struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Artist string `json:"artist"`
|
|
ArtistID string `json:"artistId"`
|
|
Year int `json:"year,omitempty"`
|
|
CoverArt string `json:"coverArt,omitempty"`
|
|
}
|
|
|
|
type AlbumFull struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Artist string `json:"artist"`
|
|
ArtistID string `json:"artistId"`
|
|
Year int `json:"year,omitempty"`
|
|
CoverArt string `json:"coverArt,omitempty"`
|
|
Song []SongRef `json:"song,omitempty"`
|
|
}
|
|
|
|
type SongFull struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Album string `json:"album"`
|
|
Artist string `json:"artist"`
|
|
AlbumID string `json:"albumId"`
|
|
ArtistID string `json:"artistId"`
|
|
Track int `json:"track,omitempty"`
|
|
Duration int `json:"duration,omitempty"`
|
|
CoverArt string `json:"coverArt,omitempty"`
|
|
}
|
|
|
|
type ScanStatus struct {
|
|
Scanning bool `json:"scanning"`
|
|
Count int `json:"count"`
|
|
FolderCount int `json:"folderCount"`
|
|
LastError string `json:"lastError,omitempty"`
|
|
StartedAt string `json:"startedAt,omitempty"`
|
|
FinishedAt string `json:"finishedAt,omitempty"`
|
|
}
|
|
|
|
type SearchResult3 struct {
|
|
Artist []ArtistRef `json:"artist,omitempty"`
|
|
Album []AlbumRef `json:"album,omitempty"`
|
|
Song []SongRef `json:"song,omitempty"`
|
|
}
|
|
|
|
type Starred2 struct {
|
|
Artist []ArtistRef `json:"artist,omitempty"`
|
|
Album []AlbumRef `json:"album,omitempty"`
|
|
Song []SongRef `json:"song,omitempty"`
|
|
}
|
|
|
|
type Playlists struct {
|
|
Playlist []PlaylistSummary `json:"playlist,omitempty"`
|
|
}
|
|
|
|
type Playlist struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Owner string `json:"owner"`
|
|
Public bool `json:"public"`
|
|
Comment string `json:"comment,omitempty"`
|
|
SongCount int `json:"songCount"`
|
|
Duration int `json:"duration,omitempty"`
|
|
Created string `json:"created,omitempty"`
|
|
Changed string `json:"changed,omitempty"`
|
|
Entry []SongRef `json:"entry,omitempty"`
|
|
}
|
|
|
|
type PlaylistSummary struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Owner string `json:"owner"`
|
|
Public bool `json:"public"`
|
|
SongCount int `json:"songCount"`
|
|
Duration int `json:"duration,omitempty"`
|
|
Created string `json:"created,omitempty"`
|
|
Changed string `json:"changed,omitempty"`
|
|
}
|
|
|
|
type ErrorRef struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func PingResponse() Envelope {
|
|
return Envelope{
|
|
SubsonicResponse: Response{
|
|
Status: "ok",
|
|
Version: "1.16.1",
|
|
Type: "temporserv",
|
|
Server: "0.1.0",
|
|
OpenAPI: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func ArtistsResponse(artists []library.Artist) Envelope {
|
|
response := PingResponse()
|
|
for _, artist := range artists {
|
|
response.SubsonicResponse.Artists = append(response.SubsonicResponse.Artists, ArtistRef{
|
|
ID: artist.ID,
|
|
Name: artist.Name,
|
|
})
|
|
}
|
|
return response
|
|
}
|
|
|
|
func RandomSongsResponse(tracks []library.Track) Envelope {
|
|
response := PingResponse()
|
|
for _, track := range tracks {
|
|
response.SubsonicResponse.RandomSong = append(response.SubsonicResponse.RandomSong, SongRef{
|
|
ID: track.ID,
|
|
Title: track.Title,
|
|
Album: track.AlbumTitle,
|
|
Artist: track.ArtistName,
|
|
AlbumID: track.AlbumID,
|
|
ArtistID: track.ArtistID,
|
|
CoverArt: track.CoverArtID,
|
|
})
|
|
}
|
|
return response
|
|
}
|
|
|
|
func ArtistResponse(artist library.ArtistDetail) Envelope {
|
|
response := PingResponse()
|
|
item := &ArtistFull{
|
|
ID: artist.ID,
|
|
Name: artist.Name,
|
|
CoverArt: artist.CoverArtID,
|
|
AlbumCount: artist.AlbumCount,
|
|
}
|
|
for _, album := range artist.Albums {
|
|
item.Albums = append(item.Albums, AlbumRef{
|
|
ID: album.ID,
|
|
Name: album.Title,
|
|
Artist: album.ArtistName,
|
|
ArtistID: album.ArtistID,
|
|
Year: album.Year,
|
|
CoverArt: album.CoverArtID,
|
|
})
|
|
}
|
|
response.SubsonicResponse.Artist = item
|
|
return response
|
|
}
|
|
|
|
func Search3Response(results library.SearchResults) Envelope {
|
|
response := PingResponse()
|
|
payload := &SearchResult3{}
|
|
for _, artist := range results.Artists {
|
|
payload.Artist = append(payload.Artist, ArtistRef{
|
|
ID: artist.ID,
|
|
Name: artist.Name,
|
|
})
|
|
}
|
|
for _, album := range results.Albums {
|
|
payload.Album = append(payload.Album, AlbumRef{
|
|
ID: album.ID,
|
|
Name: album.Title,
|
|
Artist: album.ArtistName,
|
|
ArtistID: album.ArtistID,
|
|
Year: album.Year,
|
|
CoverArt: album.CoverArtID,
|
|
})
|
|
}
|
|
for _, track := range results.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.CoverArtID,
|
|
})
|
|
}
|
|
response.SubsonicResponse.SearchResult3 = payload
|
|
return response
|
|
}
|
|
|
|
func Starred2Response(results library.StarredResults) Envelope {
|
|
response := PingResponse()
|
|
payload := &Starred2{}
|
|
for _, artist := range results.Artists {
|
|
payload.Artist = append(payload.Artist, ArtistRef{
|
|
ID: artist.ID,
|
|
Name: artist.Name,
|
|
})
|
|
}
|
|
for _, album := range results.Albums {
|
|
payload.Album = append(payload.Album, AlbumRef{
|
|
ID: album.ID,
|
|
Name: album.Title,
|
|
Artist: album.ArtistName,
|
|
ArtistID: album.ArtistID,
|
|
Year: album.Year,
|
|
CoverArt: album.CoverArtID,
|
|
})
|
|
}
|
|
for _, track := range results.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.CoverArtID,
|
|
})
|
|
}
|
|
response.SubsonicResponse.Starred2 = payload
|
|
return response
|
|
}
|
|
|
|
func PlaylistsResponse(owner string, playlists []playlist.Summary) Envelope {
|
|
response := PingResponse()
|
|
payload := &Playlists{}
|
|
for _, item := range playlists {
|
|
payload.Playlist = append(payload.Playlist, PlaylistSummary{
|
|
ID: item.ID,
|
|
Name: item.Name,
|
|
Owner: owner,
|
|
Public: item.Public,
|
|
SongCount: item.SongCount,
|
|
Duration: item.Duration,
|
|
Created: formatTime(item.CreatedAt),
|
|
Changed: formatTime(item.UpdatedAt),
|
|
})
|
|
}
|
|
response.SubsonicResponse.Playlists = payload
|
|
return response
|
|
}
|
|
|
|
func PlaylistResponse(owner string, detail playlist.Detail) Envelope {
|
|
response := PingResponse()
|
|
item := &Playlist{
|
|
ID: detail.ID,
|
|
Name: detail.Name,
|
|
Owner: owner,
|
|
Public: detail.Public,
|
|
Comment: detail.Comment,
|
|
SongCount: detail.SongCount,
|
|
Duration: detail.Duration,
|
|
Created: formatTime(detail.CreatedAt),
|
|
Changed: formatTime(detail.UpdatedAt),
|
|
}
|
|
for _, track := range detail.Tracks {
|
|
item.Entry = append(item.Entry, SongRef{
|
|
ID: track.ID,
|
|
Title: track.Title,
|
|
Album: track.AlbumTitle,
|
|
Artist: track.ArtistName,
|
|
AlbumID: track.AlbumID,
|
|
ArtistID: track.ArtistID,
|
|
CoverArt: track.CoverArtID,
|
|
})
|
|
}
|
|
response.SubsonicResponse.Playlist = item
|
|
return response
|
|
}
|
|
|
|
func AlbumResponse(album library.AlbumDetail) Envelope {
|
|
response := PingResponse()
|
|
item := &AlbumFull{
|
|
ID: album.ID,
|
|
Name: album.Title,
|
|
Artist: album.ArtistName,
|
|
ArtistID: album.ArtistID,
|
|
Year: album.Year,
|
|
CoverArt: album.CoverArtID,
|
|
}
|
|
for _, track := range album.Tracks {
|
|
item.Song = append(item.Song, SongRef{
|
|
ID: track.ID,
|
|
Title: track.Title,
|
|
Album: track.AlbumTitle,
|
|
Artist: track.ArtistName,
|
|
})
|
|
}
|
|
response.SubsonicResponse.Album = item
|
|
return response
|
|
}
|
|
|
|
func SongResponse(track library.Track) Envelope {
|
|
response := PingResponse()
|
|
response.SubsonicResponse.Song = &SongFull{
|
|
ID: track.ID,
|
|
Title: track.Title,
|
|
Album: track.AlbumTitle,
|
|
Artist: track.ArtistName,
|
|
AlbumID: track.AlbumID,
|
|
ArtistID: track.ArtistID,
|
|
Track: track.TrackNumber,
|
|
Duration: track.DurationSecs,
|
|
CoverArt: track.CoverArtID,
|
|
}
|
|
return response
|
|
}
|
|
|
|
func ScanStatusResponse(status scanner.Status) Envelope {
|
|
response := PingResponse()
|
|
response.SubsonicResponse.ScanStatus = &ScanStatus{
|
|
Scanning: status.Scanning,
|
|
Count: status.Tracks,
|
|
FolderCount: status.Albums,
|
|
LastError: status.LastError,
|
|
StartedAt: formatTime(status.StartedAt),
|
|
FinishedAt: formatTime(status.FinishedAt),
|
|
}
|
|
return response
|
|
}
|
|
|
|
func ErrorResponse(code int, message string) Envelope {
|
|
response := PingResponse()
|
|
response.SubsonicResponse.Status = "failed"
|
|
response.SubsonicResponse.Error = &ErrorRef{
|
|
Code: code,
|
|
Message: message,
|
|
}
|
|
return response
|
|
}
|
|
|
|
func formatTime(value time.Time) string {
|
|
if value.IsZero() {
|
|
return ""
|
|
}
|
|
return value.Format(time.RFC3339)
|
|
}
|