more Jellyfin integration work
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package jellyfin
|
||||
|
||||
import "github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
|
||||
const (
|
||||
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)"
|
||||
)
|
||||
|
||||
func (j *jellyfinMediaProvider) AlbumSortOrders() []string {
|
||||
return []string{
|
||||
AlbumSortRecentlyAdded,
|
||||
AlbumSortRecentlyPlayed,
|
||||
AlbumSortFrequentlyPlayed,
|
||||
AlbumSortRandom,
|
||||
AlbumSortTitleAZ,
|
||||
AlbumSortArtistAZ,
|
||||
AlbumSortYearAscending,
|
||||
AlbumSortYearDescending,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) IterateAlbums(sortOrder string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator {
|
||||
return nil
|
||||
// TODO: unimplemented
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) SearchAlbums(searchQuery string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator {
|
||||
return nil
|
||||
// TODO: unimplemented
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) IterateTracks(searchQuery string) mediaprovider.TrackIterator {
|
||||
return nil
|
||||
// TODO: unimplemented
|
||||
}
|
||||
@@ -2,13 +2,34 @@ package jellyfin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dweymouth/go-jellyfin"
|
||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
"github.com/dweymouth/supersonic/sharedutil"
|
||||
)
|
||||
|
||||
const cacheValidDurationSeconds = 60
|
||||
|
||||
var _ mediaprovider.MediaProvider = (*jellyfinMediaProvider)(nil)
|
||||
|
||||
type jellyfinMediaProvider struct {
|
||||
client *jellyfin.Client
|
||||
prefetchCoverCB func(coverArtID string)
|
||||
|
||||
genresCached []*mediaprovider.Genre
|
||||
genresCachedAt int64 // unix
|
||||
}
|
||||
|
||||
func newJellyfinMediaProvider(cli *jellyfin.Client) mediaprovider.MediaProvider {
|
||||
return &jellyfinMediaProvider{
|
||||
client: cli,
|
||||
genresCached: make([]*mediaprovider.Genre, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) SetPrefetchCoverCallback(cb func(coverArtID string)) {
|
||||
@@ -22,3 +43,275 @@ func (jellyfinMediaProvider) CreatePlaylist(name string, trackIDs []string) erro
|
||||
func (j *jellyfinMediaProvider) DeletePlaylist(id string) error {
|
||||
return j.client.DeletePlaylist(id)
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) EditPlaylist(id, name, description string, public bool) error {
|
||||
return errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) EditPlaylistTracks(id string, trackIDsToAdd []string, trackIndexesToRemove []int) error {
|
||||
return errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) GetAlbum(albumID string) (*mediaprovider.AlbumWithTracks, error) {
|
||||
al, err := j.client.GetAlbum(albumID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var opts jellyfin.QueryOpts
|
||||
opts.Filter.ParentID = albumID
|
||||
tr, err := j.client.GetSongs(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
album := &mediaprovider.AlbumWithTracks{}
|
||||
fillAlbum(al, &album.Album)
|
||||
album.Tracks = sharedutil.MapSlice(tr, toTrack)
|
||||
return album, nil
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) GetAlbumInfo(albumID string) (*mediaprovider.AlbumInfo, error) {
|
||||
return nil, errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) GetArtist(artistID string) (*mediaprovider.ArtistWithAlbums, error) {
|
||||
return nil, errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) GetArtistInfo(artistID string) (*mediaprovider.ArtistInfo, error) {
|
||||
return nil, errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) GetArtists() ([]*mediaprovider.Artist, error) {
|
||||
return nil, errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) GetTrack(trackID string) (*mediaprovider.Track, error) {
|
||||
tr, err := j.client.GetSong(trackID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toTrack(tr), nil
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) GetTopTracks(artist mediaprovider.Artist, limit int) ([]*mediaprovider.Track, error) {
|
||||
var opts jellyfin.QueryOpts
|
||||
opts.Paging.Limit = limit
|
||||
opts.Filter.ArtistID = artist.ID
|
||||
opts.Sort.Field = "CommunityRating"
|
||||
opts.Sort.Mode = jellyfin.SortDesc
|
||||
tr, err := j.client.GetSongs(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sharedutil.MapSlice(tr, toTrack), nil
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) GetRandomTracks(genreName string, limit int) ([]*mediaprovider.Track, error) {
|
||||
var opts jellyfin.QueryOpts
|
||||
opts.Paging.Limit = limit
|
||||
opts.Filter.Genres = []jellyfin.NameID{{Name: genreName}}
|
||||
opts.Sort.Field = "Random"
|
||||
tr, err := j.client.GetSongs(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sharedutil.MapSlice(tr, toTrack), nil
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) GetSimilarTracks(artistID string, limit int) ([]*mediaprovider.Track, error) {
|
||||
tr, err := j.client.GetSimilarSongs(artistID, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sharedutil.MapSlice(tr, toTrack), nil
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) GetCoverArt(id string, size int) (image.Image, error) {
|
||||
return j.client.GetItemImage(id, "Primary", size, 92)
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) GetFavorites() (mediaprovider.Favorites, error) {
|
||||
return mediaprovider.Favorites{}, errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) GetGenres() ([]*mediaprovider.Genre, error) {
|
||||
if j.genresCached != nil && time.Now().Unix()-j.genresCachedAt < cacheValidDurationSeconds {
|
||||
return j.genresCached, nil
|
||||
}
|
||||
|
||||
g, err := j.client.GetGenres(jellyfin.Paging{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
j.genresCached = sharedutil.MapSlice(g, func(g jellyfin.NameID) *mediaprovider.Genre {
|
||||
return &mediaprovider.Genre{
|
||||
Name: g.Name,
|
||||
AlbumCount: -1, // unsupported by Jellyfin
|
||||
TrackCount: -1, // unsupported by Jellyfin
|
||||
}
|
||||
})
|
||||
j.genresCachedAt = time.Now().Unix()
|
||||
return j.genresCached, nil
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error) {
|
||||
pl, err := j.client.GetPlaylists()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sharedutil.MapSlice(pl, toPlaylist), nil
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.PlaylistWithTracks, error) {
|
||||
tr, err := j.client.GetPlaylistSongs(playlistID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pl, err := j.client.GetPlaylist(playlistID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
playlist := &mediaprovider.PlaylistWithTracks{
|
||||
Tracks: sharedutil.MapSlice(tr, toTrack),
|
||||
}
|
||||
fillPlaylist(pl, &playlist.Playlist)
|
||||
return playlist, nil
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) ReplacePlaylistTracks(playlistID string, trackIDs []string) error {
|
||||
return errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) SetFavorite(params mediaprovider.RatingFavoriteParameters, favorite bool) error {
|
||||
var allIDs []string
|
||||
allIDs = append(allIDs, params.AlbumIDs...)
|
||||
allIDs = append(allIDs, params.ArtistIDs...)
|
||||
allIDs = append(allIDs, params.TrackIDs...)
|
||||
|
||||
// Jellyfin doesn't allow bulk setting favorites.
|
||||
// To not overwhelm the server with requests, set favorite for
|
||||
// only 5 items at a time concurrently
|
||||
batchSize := 5
|
||||
var err error
|
||||
batchSetFavorite := func(offs int, wg *sync.WaitGroup) {
|
||||
for i := 0; i < batchSize && offs+i < len(allIDs); i++ {
|
||||
wg.Add(1)
|
||||
go func(idx int) {
|
||||
newErr := j.client.SetFavorite(allIDs[idx], favorite)
|
||||
if err == nil && newErr != nil {
|
||||
err = newErr
|
||||
}
|
||||
wg.Done()
|
||||
}(offs + i)
|
||||
}
|
||||
}
|
||||
|
||||
numBatches := int(math.Ceil(float64(len(allIDs)) / float64(batchSize)))
|
||||
for i := 0; i < numBatches; i++ {
|
||||
var wg sync.WaitGroup
|
||||
batchSetFavorite(i*batchSize, &wg)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) SetRating(params mediaprovider.RatingFavoriteParameters, rating int) error {
|
||||
return errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) GetStreamURL(trackID string, forceRaw bool) (string, error) {
|
||||
return j.client.GetStreamURL(trackID)
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) DownloadTrack(trackID string) (io.Reader, error) {
|
||||
return nil, errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (j *jellyfinMediaProvider) Scrobble(trackID string, submission bool) error {
|
||||
return errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func (s *jellyfinMediaProvider) RescanLibrary() error {
|
||||
return errors.ErrUnsupported
|
||||
}
|
||||
|
||||
func toTrack(ch *jellyfin.Song) *mediaprovider.Track {
|
||||
if ch == nil {
|
||||
return nil
|
||||
}
|
||||
var artistNames, artistIDs []string
|
||||
for _, a := range ch.Artists {
|
||||
artistIDs = append(artistIDs, a.ID)
|
||||
artistNames = append(artistNames, a.Name)
|
||||
}
|
||||
|
||||
t := &mediaprovider.Track{
|
||||
ID: ch.Id,
|
||||
//CoverArtID: ch.CoverArt,
|
||||
ParentID: ch.AlbumID,
|
||||
Name: ch.Name,
|
||||
Duration: int(ch.RunTimeTicks / 1_000_000),
|
||||
TrackNumber: ch.IndexNumber,
|
||||
DiscNumber: ch.DiscNumber,
|
||||
//Genre: ch.Genres,
|
||||
ArtistIDs: artistIDs,
|
||||
ArtistNames: artistNames,
|
||||
Album: ch.Album,
|
||||
AlbumID: ch.AlbumID,
|
||||
Year: ch.ProductionYear,
|
||||
Rating: ch.UserData.Rating,
|
||||
Favorite: ch.UserData.IsFavorite,
|
||||
PlayCount: ch.UserData.PlayCount,
|
||||
}
|
||||
if len(ch.MediaSources) > 0 {
|
||||
t.FilePath = ch.MediaSources[0].Path
|
||||
t.Size = int64(ch.MediaSources[0].Size)
|
||||
t.BitRate = ch.MediaSources[0].Bitrate
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func toAlbum(a *jellyfin.Album) *mediaprovider.Album {
|
||||
album := &mediaprovider.Album{}
|
||||
fillAlbum(a, album)
|
||||
return album
|
||||
}
|
||||
|
||||
func fillAlbum(a *jellyfin.Album, album *mediaprovider.Album) {
|
||||
var artistNames, artistIDs []string
|
||||
for _, a := range a.Artists {
|
||||
artistIDs = append(artistIDs, a.ID)
|
||||
artistNames = append(artistNames, a.Name)
|
||||
}
|
||||
|
||||
album.ID = a.ID
|
||||
//album.CoverArtID = a.CoverArt
|
||||
album.Name = a.Name
|
||||
album.Duration = int(a.RunTimeTicks / 1_000_000)
|
||||
album.ArtistIDs = artistIDs
|
||||
album.ArtistNames = artistNames
|
||||
album.Year = a.Year
|
||||
//album.TrackCount = a.
|
||||
album.Genres = a.Genres
|
||||
album.Favorite = a.UserData.IsFavorite
|
||||
}
|
||||
|
||||
func toPlaylist(p *jellyfin.Playlist) *mediaprovider.Playlist {
|
||||
pl := &mediaprovider.Playlist{}
|
||||
fillPlaylist(p, pl)
|
||||
return pl
|
||||
}
|
||||
|
||||
func fillPlaylist(p *jellyfin.Playlist, pl *mediaprovider.Playlist) {
|
||||
pl.Name = p.Name
|
||||
pl.ID = p.ID
|
||||
//CoverArtID = pl.CoverArt
|
||||
pl.Description = p.Overview
|
||||
//.Owner = pl.Owner
|
||||
//Public = pl.Public
|
||||
pl.TrackCount = p.SongCount
|
||||
pl.Duration = int(p.RunTimeTicks / 1_000_000)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package jellyfin
|
||||
|
||||
import (
|
||||
jellyfinCli "github.com/dweymouth/go-jellyfin"
|
||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
)
|
||||
|
||||
type JellyfinServer struct {
|
||||
jellyfinCli.Client
|
||||
}
|
||||
|
||||
func (j *JellyfinServer) MediaProvider() mediaprovider.MediaProvider {
|
||||
return newJellyfinMediaProvider(&j.Client)
|
||||
}
|
||||
|
||||
func (j *JellyfinServer) Ping() bool {
|
||||
return false // TODO: unimplemented
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package jellyfin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
)
|
||||
|
||||
func (s *jellyfinMediaProvider) SearchAll(searchQuery string, maxResults int) ([]*mediaprovider.SearchResult, error) {
|
||||
return nil, errors.New("unimplemented")
|
||||
}
|
||||
+39
-17
@@ -6,9 +6,12 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/dweymouth/go-jellyfin"
|
||||
"github.com/dweymouth/go-subsonic/subsonic"
|
||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
jellyfinMP "github.com/dweymouth/supersonic/backend/mediaprovider/jellyfin"
|
||||
subsonicMP "github.com/dweymouth/supersonic/backend/mediaprovider/subsonic"
|
||||
"github.com/dweymouth/supersonic/res"
|
||||
"github.com/google/uuid"
|
||||
"github.com/zalando/go-keyring"
|
||||
)
|
||||
@@ -159,23 +162,42 @@ func (s *ServerManager) SetServerPassword(server *ServerConfig, password string)
|
||||
func (s *ServerManager) connect(connection ServerConnection, password string) (mediaprovider.Server, error) {
|
||||
var cli, altCli mediaprovider.Server
|
||||
|
||||
cli = &subsonicMP.SubsonicServer{
|
||||
Client: subsonic.Client{
|
||||
Client: &http.Client{Timeout: 10 * time.Second},
|
||||
BaseUrl: connection.Hostname,
|
||||
User: connection.Username,
|
||||
PasswordAuth: connection.LegacyAuth,
|
||||
ClientName: "supersonic",
|
||||
},
|
||||
}
|
||||
altCli = &subsonicMP.SubsonicServer{
|
||||
Client: subsonic.Client{
|
||||
Client: &http.Client{Timeout: 10 * time.Second},
|
||||
BaseUrl: connection.AltHostname,
|
||||
User: connection.Username,
|
||||
PasswordAuth: connection.LegacyAuth,
|
||||
ClientName: "supersonic",
|
||||
},
|
||||
if connection.ServerType == ServerTypeJellyfin {
|
||||
cli = &jellyfinMP.JellyfinServer{
|
||||
Client: jellyfin.Client{
|
||||
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
||||
BaseURL: connection.Hostname,
|
||||
ClientName: res.AppName,
|
||||
ClientVersion: res.AppVersion,
|
||||
},
|
||||
}
|
||||
altCli = &jellyfinMP.JellyfinServer{
|
||||
Client: jellyfin.Client{
|
||||
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
||||
BaseURL: connection.AltHostname,
|
||||
ClientName: res.AppName,
|
||||
ClientVersion: res.AppVersion,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
cli = &subsonicMP.SubsonicServer{
|
||||
Client: subsonic.Client{
|
||||
Client: &http.Client{Timeout: 10 * time.Second},
|
||||
BaseUrl: connection.Hostname,
|
||||
User: connection.Username,
|
||||
PasswordAuth: connection.LegacyAuth,
|
||||
ClientName: res.AppName,
|
||||
},
|
||||
}
|
||||
altCli = &subsonicMP.SubsonicServer{
|
||||
Client: subsonic.Client{
|
||||
Client: &http.Client{Timeout: 10 * time.Second},
|
||||
BaseUrl: connection.AltHostname,
|
||||
User: connection.Username,
|
||||
PasswordAuth: connection.LegacyAuth,
|
||||
ClientName: res.AppName,
|
||||
},
|
||||
}
|
||||
}
|
||||
pingChan := make(chan bool, 2) // false for primary hostname, true for alternate
|
||||
pingFunc := func(delay time.Duration, cli mediaprovider.Server, val bool) {
|
||||
|
||||
@@ -6,6 +6,7 @@ require (
|
||||
fyne.io/fyne/v2 v2.4.1
|
||||
github.com/20after4/configdir v0.1.1
|
||||
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
|
||||
github.com/dweymouth/go-jellyfin v0.0.0-20231112164127-119b53593792
|
||||
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee
|
||||
github.com/dweymouth/go-subsonic v0.0.0-20231105161622-54b5aec28363
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
@@ -22,7 +23,6 @@ require (
|
||||
github.com/alessio/shellescape v1.4.1 // indirect
|
||||
github.com/danieljoos/wincred v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dweymouth/go-jellyfin v0.0.0-20231112010253-d93b76137091 // indirect
|
||||
github.com/fredbi/uri v1.0.0 // indirect
|
||||
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
|
||||
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect
|
||||
|
||||
@@ -71,8 +71,8 @@ github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+
|
||||
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E=
|
||||
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20231110162149-a0e470497555 h1:8S+d0LuwdTUEipEzFeXp8rNwTQD47dBdDOg9+FI1+Vw=
|
||||
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20231110162149-a0e470497555/go.mod h1:AWM1iPM2YfliduZ4u/kQzP9E6ARIWm0gg+57GpYzWro=
|
||||
github.com/dweymouth/go-jellyfin v0.0.0-20231112010253-d93b76137091 h1:UiyWNmeig46pkNCj7Pqc0Ta4JdxYKeXnBwuX+NOFCyM=
|
||||
github.com/dweymouth/go-jellyfin v0.0.0-20231112010253-d93b76137091/go.mod h1:BMwS4vdjEYf1gmjPGSKCzWP/I6YlI6fkefJ9nsjBjaU=
|
||||
github.com/dweymouth/go-jellyfin v0.0.0-20231112164127-119b53593792 h1:Be+Y0AnYaSCeCm2+TUjw5TzowttJbsZsd5a7Wn3dqOI=
|
||||
github.com/dweymouth/go-jellyfin v0.0.0-20231112164127-119b53593792/go.mod h1:BMwS4vdjEYf1gmjPGSKCzWP/I6YlI6fkefJ9nsjBjaU=
|
||||
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY=
|
||||
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0=
|
||||
github.com/dweymouth/go-subsonic v0.0.0-20231105161622-54b5aec28363 h1:MIH7MAWWPPVRKEKxz+RJubn+ycyQPimHn1Zvoxs1KRI=
|
||||
@@ -337,7 +337,6 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
|
||||
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
|
||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
@@ -499,7 +498,6 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
|
||||
@@ -196,8 +196,16 @@ func NewGenreList(sorting widgets.ListHeaderSort) *GenreList {
|
||||
row := item.(*GenreListRow)
|
||||
row.Item = a.genres[id]
|
||||
row.nameLabel.Text = row.Item.Name
|
||||
row.albumCountLabel.Text = strconv.Itoa(row.Item.AlbumCount)
|
||||
row.trackCountLabel.Text = strconv.Itoa(row.Item.TrackCount)
|
||||
if row.Item.AlbumCount >= 0 {
|
||||
row.albumCountLabel.Text = strconv.Itoa(row.Item.AlbumCount)
|
||||
} else {
|
||||
row.albumCountLabel.Text = ""
|
||||
}
|
||||
if row.Item.TrackCount >= 0 {
|
||||
row.trackCountLabel.Text = strconv.Itoa(row.Item.TrackCount)
|
||||
} else {
|
||||
row.trackCountLabel.Text = ""
|
||||
}
|
||||
row.Refresh()
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user