346 lines
8.4 KiB
Go
346 lines
8.4 KiB
Go
package mediaprovider
|
|
|
|
import (
|
|
"image"
|
|
"io"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/deluan/sanitize"
|
|
)
|
|
|
|
const (
|
|
// set of all supported album sorts across all media providers
|
|
// these strings may be translated
|
|
AlbumSortRecentlyAdded string = "Recently Added"
|
|
AlbumSortRecentlyPlayed string = "Recently Played"
|
|
AlbumSortFrequentlyPlayed string = "Frequently Played"
|
|
AlbumSortRandom string = "Random"
|
|
AlbumSortTitleAZ string = "Title (A-Z)"
|
|
AlbumSortArtistAZ string = "Artist (A-Z)"
|
|
AlbumSortYearAscending string = "Year (ascending)"
|
|
AlbumSortYearDescending string = "Year (descending)"
|
|
|
|
// set of all supported artist sorts across all media providers
|
|
// these strings may be translated
|
|
ArtistSortAlbumCount string = "Album Count"
|
|
ArtistSortNameAZ string = "Name (A-Z)"
|
|
ArtistSortRandom string = "Random"
|
|
)
|
|
|
|
type MediaIterator[M any] interface {
|
|
Next() *M
|
|
}
|
|
|
|
type (
|
|
ArtistIterator = MediaIterator[Artist]
|
|
AlbumIterator = MediaIterator[Album]
|
|
TrackIterator = MediaIterator[Track]
|
|
)
|
|
|
|
type MediaFilter[M, F any] interface {
|
|
Options() F
|
|
SetOptions(F)
|
|
Clone() MediaFilter[M, F]
|
|
IsNil() bool
|
|
Matches(*M) bool
|
|
}
|
|
|
|
type AlbumFilter = MediaFilter[Album, AlbumFilterOptions]
|
|
|
|
type AlbumFilterOptions struct {
|
|
MinYear int
|
|
MaxYear int // 0 == unset/match any
|
|
Genres []string // len(0) == unset/match any
|
|
|
|
ExcludeFavorited bool // mut. exc. with ExcludeUnfavorited
|
|
ExcludeUnfavorited bool // mut. exc. with ExcludeFavorited
|
|
}
|
|
|
|
// Clone returns a deep copy of the filter options
|
|
func (o AlbumFilterOptions) Clone() AlbumFilterOptions {
|
|
genres := make([]string, len(o.Genres))
|
|
copy(genres, o.Genres)
|
|
return AlbumFilterOptions{
|
|
MinYear: o.MinYear,
|
|
MaxYear: o.MaxYear,
|
|
Genres: genres,
|
|
ExcludeFavorited: o.ExcludeFavorited,
|
|
ExcludeUnfavorited: o.ExcludeUnfavorited,
|
|
}
|
|
}
|
|
|
|
type albumFilter struct {
|
|
options AlbumFilterOptions
|
|
}
|
|
|
|
func NewAlbumFilter(options AlbumFilterOptions) *albumFilter {
|
|
return &albumFilter{options}
|
|
}
|
|
|
|
func (a albumFilter) Options() AlbumFilterOptions {
|
|
return a.options
|
|
}
|
|
|
|
func (a *albumFilter) SetOptions(options AlbumFilterOptions) {
|
|
a.options = options
|
|
}
|
|
|
|
// Clone returns a deep copy of the filter
|
|
func (a albumFilter) Clone() AlbumFilter {
|
|
return NewAlbumFilter(a.options.Clone())
|
|
}
|
|
|
|
// Returns true if the filter is the nil filter - i.e. matches everything
|
|
func (a albumFilter) IsNil() bool {
|
|
return a.options.MinYear == 0 && a.options.MaxYear == 0 &&
|
|
len(a.options.Genres) == 0 &&
|
|
!a.options.ExcludeFavorited && !a.options.ExcludeUnfavorited
|
|
}
|
|
|
|
func (f albumFilter) Matches(album *Album) bool {
|
|
if album == nil {
|
|
return false
|
|
}
|
|
if f.options.ExcludeFavorited && album.Favorite {
|
|
return false
|
|
}
|
|
if f.options.ExcludeUnfavorited && !album.Favorite {
|
|
return false
|
|
}
|
|
if y := album.YearOrZero(); y < f.options.MinYear || (f.options.MaxYear > 0 && y > f.options.MaxYear) {
|
|
return false
|
|
}
|
|
if len(f.options.Genres) == 0 {
|
|
return true
|
|
}
|
|
return genresMatch(f.options.Genres, album.Genres)
|
|
}
|
|
|
|
type ArtistFilter = MediaFilter[Artist, ArtistFilterOptions]
|
|
|
|
type ArtistFilterOptions struct {
|
|
SearchQuery string
|
|
}
|
|
|
|
// Clone returns a deep copy of the filter options
|
|
func (o ArtistFilterOptions) Clone() ArtistFilterOptions {
|
|
return ArtistFilterOptions{
|
|
SearchQuery: o.SearchQuery,
|
|
}
|
|
}
|
|
|
|
type artistFilter struct {
|
|
options ArtistFilterOptions
|
|
}
|
|
|
|
func NewArtistFilter(options ArtistFilterOptions) *artistFilter {
|
|
return &artistFilter{options}
|
|
}
|
|
|
|
func (a artistFilter) Options() ArtistFilterOptions {
|
|
return a.options
|
|
}
|
|
|
|
func (a *artistFilter) SetOptions(o ArtistFilterOptions) {
|
|
a.options = o
|
|
}
|
|
|
|
// Clone returns a deep copy of the filter
|
|
func (a artistFilter) Clone() ArtistFilter {
|
|
return NewArtistFilter(a.options.Clone())
|
|
}
|
|
|
|
// Returns true if the filter is the nil filter - i.e. matches everything
|
|
func (a artistFilter) IsNil() bool {
|
|
return a.options.SearchQuery == ""
|
|
}
|
|
|
|
func (f artistFilter) Matches(artist *Artist) bool {
|
|
if artist == nil {
|
|
return false
|
|
}
|
|
if f.options.SearchQuery != "" && !strings.Contains(
|
|
sanitize.Accents(strings.ToLower(artist.Name)),
|
|
sanitize.Accents(strings.ToLower(f.options.SearchQuery)),
|
|
) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
type RatingFavoriteParameters struct {
|
|
AlbumIDs []string
|
|
ArtistIDs []string
|
|
TrackIDs []string
|
|
}
|
|
|
|
type Favorites struct {
|
|
Albums []*Album
|
|
Artists []*Artist
|
|
Tracks []*Track
|
|
}
|
|
|
|
type LoginResponse struct {
|
|
Error error
|
|
IsAuthError bool
|
|
}
|
|
|
|
type TranscodeSettings struct {
|
|
Codec string
|
|
BitRateKBPS int
|
|
}
|
|
|
|
type Server interface {
|
|
Login(username, password string) LoginResponse
|
|
MediaProvider() MediaProvider
|
|
}
|
|
|
|
type MediaProvider interface {
|
|
SetPrefetchCoverCallback(cb func(coverArtID string))
|
|
|
|
// GetLibraries gets the list of top-level music libraries
|
|
// (musicFolders in Subsonic)
|
|
GetLibraries() ([]Library, error)
|
|
|
|
// SetLibrary sets the current library that all other
|
|
// MediaProvider API calls will filter to. Use empty string
|
|
// to reset to all libraries.
|
|
SetLibrary(id string) error
|
|
|
|
GetTrack(trackID string) (*Track, error)
|
|
|
|
GetAlbum(albumID string) (*AlbumWithTracks, error)
|
|
|
|
GetAlbumInfo(albumID string) (*AlbumInfo, error)
|
|
|
|
GetArtist(artistID string) (*ArtistWithAlbums, error)
|
|
|
|
GetArtistTracks(artistID string) ([]*Track, error)
|
|
|
|
GetArtistInfo(artistID string) (*ArtistInfo, error)
|
|
|
|
GetPlaylist(playlistID string) (*PlaylistWithTracks, error)
|
|
|
|
GetCoverArt(coverArtID string, size int) (image.Image, error)
|
|
|
|
AlbumSortOrders() []string
|
|
|
|
IterateAlbums(sortOrder string, filter AlbumFilter) AlbumIterator
|
|
|
|
IterateTracks(searchQuery string) TrackIterator
|
|
|
|
SearchAlbums(searchQuery string, filter AlbumFilter) AlbumIterator
|
|
|
|
SearchAll(searchQuery string, maxResults int) ([]*SearchResult, error)
|
|
|
|
GetRandomTracks(genre string, count int) ([]*Track, error)
|
|
|
|
GetSimilarTracks(artistID string, count int) ([]*Track, error)
|
|
|
|
GetSongRadio(trackID string, count int) ([]*Track, error)
|
|
|
|
ArtistSortOrders() []string
|
|
|
|
IterateArtists(sortOrder string, filter ArtistFilter) ArtistIterator
|
|
|
|
SearchArtists(searchQuery string, filter ArtistFilter) ArtistIterator
|
|
|
|
GetGenres() ([]*Genre, error)
|
|
|
|
GetFavorites() (Favorites, error)
|
|
|
|
GetStreamURL(trackID string, transcodeSettings *TranscodeSettings, forceRaw bool) (string, error)
|
|
|
|
GetTopTracks(artist Artist, count int) ([]*Track, error)
|
|
|
|
SetFavorite(params RatingFavoriteParameters, favorite bool) error
|
|
|
|
GetPlaylists() ([]*Playlist, error)
|
|
|
|
CreatePlaylist(name string, trackIDs []string) error
|
|
|
|
CanMakePublicPlaylist() bool
|
|
|
|
EditPlaylist(id, name, description string, public bool) error
|
|
|
|
AddPlaylistTracks(id string, trackIDsToAdd []string) error
|
|
|
|
RemovePlaylistTracks(id string, trackIdxsToRemove []int) error
|
|
|
|
ReplacePlaylistTracks(id string, trackIDs []string) error
|
|
|
|
DeletePlaylist(id string) error
|
|
|
|
// True if the `submission` parameter to TrackEndedPlayback will be respected
|
|
// If false, the begin playback scrobble registers a play count immediately
|
|
// when TrackBeganPlayback is invoked.
|
|
ClientDecidesScrobble() bool
|
|
|
|
TrackBeganPlayback(trackID string) error
|
|
|
|
TrackEndedPlayback(trackID string, positionSecs int, submission bool) error
|
|
|
|
DownloadTrack(trackID string) (io.Reader, error)
|
|
|
|
RescanLibrary() error
|
|
}
|
|
|
|
type SupportsRating interface {
|
|
SetRating(params RatingFavoriteParameters, rating int) error
|
|
}
|
|
|
|
type SupportsSharing interface {
|
|
CreateShareURL(id string) (*url.URL, error)
|
|
CanShareArtists() bool
|
|
}
|
|
|
|
type CanSavePlayQueue interface {
|
|
SavePlayQueue(trackIDs []string, currentTrackPos int, timeSeconds int) error
|
|
GetPlayQueue() (*SavedPlayQueue, error)
|
|
}
|
|
|
|
type LyricsProvider interface {
|
|
GetLyrics(track *Track) (*Lyrics, error)
|
|
}
|
|
|
|
type RadioProvider interface {
|
|
GetRadioStation(id string) (*RadioStation, error)
|
|
GetRadioStations() ([]*RadioStation, error)
|
|
}
|
|
|
|
type JukeboxProvider interface {
|
|
JukeboxStart() error
|
|
JukeboxStop() error
|
|
JukeboxSeek(idx, seconds int) error
|
|
JukeboxClear() error
|
|
JukeboxAdd(trackID string) error
|
|
JukeboxRemove(idx int) error
|
|
JukeboxGetStatus() (*JukeboxStatus, error)
|
|
|
|
// Performs a Clear followed by an Add to set the queue
|
|
// to contain a single track
|
|
JukeboxSet(trackID string) error
|
|
|
|
// Sets the volume of the jukebox player (0-100)
|
|
JukeboxSetVolume(vol int) error
|
|
}
|
|
|
|
type JukeboxStatus struct {
|
|
Volume int
|
|
CurrentTrack int
|
|
Playing bool
|
|
PositionSeconds float64
|
|
}
|
|
|
|
func genresMatch(filterGenres, albumGenres []string) bool {
|
|
for _, g1 := range filterGenres {
|
|
for _, g2 := range albumGenres {
|
|
if strings.EqualFold(g1, g2) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|