Merge pull request #298 from dweymouth/releasetypes

Show album release types in page header
This commit is contained in:
Drew Weymouth
2023-12-16 15:23:08 -08:00
committed by GitHub
9 changed files with 173 additions and 27 deletions
@@ -440,6 +440,7 @@ func fillAlbum(a *jellyfin.Album, album *mediaprovider.Album) {
album.TrackCount = a.ChildCount
album.Genres = a.Genres
album.Favorite = a.UserData.IsFavorite
album.ReleaseTypes = mediaprovider.ReleaseTypeAlbum
}
func (j *jellyfinMediaProvider) toPlaylist(p *jellyfin.Playlist) *mediaprovider.Playlist {
+41 -10
View File
@@ -1,16 +1,47 @@
package mediaprovider
// Bit field flag for the ReleaseTypes property
type ReleaseType = int32
// Bit field of release types
type ReleaseTypes = int32
// Set of possible release types
// Taken from Picard:
//
// (a) https://picard-docs.musicbrainz.org/en/config/options_releases.html
// (b) https://musicbrainz.org/doc/Release_Group/Type
const (
ReleaseTypeAlbum ReleaseType = 0x0001
ReleaseTypeAudiobook ReleaseType = 0x0002
ReleaseTypeAudioDrama ReleaseType = 0x0004
ReleaseTypeBroadcast ReleaseType = 0x0008
ReleaseTypeCompilation ReleaseType = 0x0010
ReleaseTypeDemo ReleaseType = 0x0020
ReleaseTypeDJMix ReleaseType = 0x0040
ReleaseTypeEP ReleaseType = 0x0080
ReleaseTypeFieldRecording ReleaseType = 0x0100
ReleaseTypeInterview ReleaseType = 0x0200
ReleaseTypeLive ReleaseType = 0x0400
ReleaseTypeMixtape ReleaseType = 0x0800
ReleaseTypeRemix ReleaseType = 0x1000
ReleaseTypeSingle ReleaseType = 0x2000
ReleaseTypeSoundtrack ReleaseType = 0x4000
ReleaseTypeSpokenWord ReleaseType = 0x8000
)
type Album struct {
ID string
CoverArtID string
Name string
Duration int
ArtistIDs []string
ArtistNames []string
Year int
Genres []string
TrackCount int
Favorite bool
ID string
CoverArtID string
Name string
Duration int
ArtistIDs []string
ArtistNames []string
Year int
Genres []string
TrackCount int
Favorite bool
ReleaseTypes ReleaseTypes
}
type AlbumWithTracks struct {
@@ -6,6 +6,7 @@ import (
"io"
"math"
"strconv"
"strings"
"sync"
"time"
@@ -404,6 +405,54 @@ func fillAlbum(subAlbum *subsonic.AlbumID3, album *mediaprovider.Album) {
album.TrackCount = subAlbum.SongCount
album.Genres = genres
album.Favorite = !subAlbum.Starred.IsZero()
album.ReleaseTypes = normalizeReleaseTypes(subAlbum.ReleaseTypes)
if subAlbum.IsCompilation {
album.ReleaseTypes |= mediaprovider.ReleaseTypeCompilation
}
}
func normalizeReleaseTypes(releaseTypes []string) mediaprovider.ReleaseTypes {
var mpReleaseTypes mediaprovider.ReleaseTypes
for _, t := range releaseTypes {
switch strings.ToLower(strings.ReplaceAll(t, " ", "")) {
case "album":
mpReleaseTypes |= mediaprovider.ReleaseTypeAlbum
case "audiobook":
mpReleaseTypes |= mediaprovider.ReleaseTypeAudiobook
case "audiodrama":
mpReleaseTypes |= mediaprovider.ReleaseTypeAudioDrama
case "broadcast":
mpReleaseTypes |= mediaprovider.ReleaseTypeBroadcast
case "compilation":
mpReleaseTypes |= mediaprovider.ReleaseTypeCompilation
case "demo":
mpReleaseTypes |= mediaprovider.ReleaseTypeDemo
case "djmix":
mpReleaseTypes |= mediaprovider.ReleaseTypeDJMix
case "ep":
mpReleaseTypes |= mediaprovider.ReleaseTypeEP
case "fieldrecording":
mpReleaseTypes |= mediaprovider.ReleaseTypeFieldRecording
case "interview":
mpReleaseTypes |= mediaprovider.ReleaseTypeInterview
case "live":
mpReleaseTypes |= mediaprovider.ReleaseTypeLive
case "mixtape":
mpReleaseTypes |= mediaprovider.ReleaseTypeMixtape
case "remix":
mpReleaseTypes |= mediaprovider.ReleaseTypeRemix
case "single":
mpReleaseTypes |= mediaprovider.ReleaseTypeSingle
case "soundtrack":
mpReleaseTypes |= mediaprovider.ReleaseTypeSoundtrack
case "spokenword":
mpReleaseTypes |= mediaprovider.ReleaseTypeSpokenWord
}
}
if mpReleaseTypes == 0 {
return mediaprovider.ReleaseTypeAlbum
}
return mpReleaseTypes
}
func toArtistFromID3(ar *subsonic.ArtistID3) *mediaprovider.Artist {
+1 -1
View File
@@ -8,7 +8,7 @@ require (
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
github.com/dweymouth/go-jellyfin v0.0.0-20231116161116-e800860bdacc
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee
github.com/dweymouth/go-subsonic v0.0.0-20231216015633-3797c9b3d94d
github.com/dweymouth/go-subsonic v0.0.0-20231216190641-c537a36d520c
github.com/fsnotify/fsnotify v1.6.0
github.com/godbus/dbus/v5 v5.1.0
github.com/google/uuid v1.3.0
+2 -2
View File
@@ -75,8 +75,8 @@ github.com/dweymouth/go-jellyfin v0.0.0-20231116161116-e800860bdacc h1:wJy4U12Ys
github.com/dweymouth/go-jellyfin v0.0.0-20231116161116-e800860bdacc/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-20231216015633-3797c9b3d94d h1:E9heA09mvlF+i+fH8Cow7NgRpQ+cbJQkdFL0OtFo624=
github.com/dweymouth/go-subsonic v0.0.0-20231216015633-3797c9b3d94d/go.mod h1:OWtcumdQsan8uM6wmx6PqKhldaCthH10CQ+vb+94kzo=
github.com/dweymouth/go-subsonic v0.0.0-20231216190641-c537a36d520c h1:48xbtXN53Rrru1Y3Ywd/FIVohH4NoRMzvxEY4aY1s0M=
github.com/dweymouth/go-subsonic v0.0.0-20231216190641-c537a36d520c/go.mod h1:OWtcumdQsan8uM6wmx6PqKhldaCthH10CQ+vb+94kzo=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+25 -7
View File
@@ -170,11 +170,13 @@ type AlbumPageHeader struct {
page *AlbumPage
cover *widgets.ImagePlaceholder
titleLabel *widget.RichText
artistLabel *widgets.MultiHyperlink
genreLabel *widgets.MultiHyperlink
miscLabel *widget.Label
cover *widgets.ImagePlaceholder
titleLabel *widget.RichText
releaseTypeLabel *widget.RichText
artistLabel *widgets.MultiHyperlink
artistLabelSpace *util.HSpace // TODO: remove when no longer needed
genreLabel *widgets.MultiHyperlink
miscLabel *widget.Label
toggleFavButton *widgets.FavoriteButton
@@ -196,10 +198,15 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
a.titleLabel.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
SizeName: theme.SizeNameHeadingText,
}
a.releaseTypeLabel = widget.NewRichText(
&widget.TextSegment{Text: "Album", Style: util.BoldRichTextStyle},
&widget.TextSegment{Text: " by", Style: widget.RichTextStyle{Inline: true}},
)
a.artistLabel = widgets.NewMultiHyperlink()
a.artistLabel.OnTapped = func(id string) {
a.page.contr.NavigateTo(controller.ArtistRoute(id))
}
a.artistLabelSpace = util.NewHSpace(0) // updated in Update
a.genreLabel = widgets.NewMultiHyperlink()
a.genreLabel.OnTapped = func(genre string) {
a.page.contr.NavigateTo(controller.GenreRoute(genre))
@@ -237,13 +244,21 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
}
a.toggleFavButton = widgets.NewFavoriteButton(func() { go a.toggleFavorited() })
// Todo: there's got to be a way to make this less convoluted. Custom layout?
// TODO: Create a nicer custom layout to set this up properly
// OR once TODO in MultiHyperlink to use RichText as a provider is solved,
// extend MultiHyperlink to support prepending rich text segments and
// don't use two separate widgets here at all.
// n.b. cannot place MultiHyperlink in a HBox or it collapses in width
artistReleaseTypeLine := container.NewStack(
a.releaseTypeLabel,
container.NewBorder(nil, nil, a.artistLabelSpace, nil, a.artistLabel))
// TODO: there's got to be a way to make this less convoluted. Custom layout?
a.container = util.AddHeaderBackground(
container.NewBorder(nil, nil, a.cover, nil,
container.New(&layouts.VboxCustomPadding{ExtraPad: -10},
a.titleLabel,
container.NewVBox(
container.New(&layouts.VboxCustomPadding{ExtraPad: -12}, a.artistLabel, a.genreLabel, a.miscLabel),
container.New(&layouts.VboxCustomPadding{ExtraPad: -12}, artistReleaseTypeLine, a.genreLabel, a.miscLabel),
container.NewVBox(
container.NewHBox(util.NewHSpace(2), playButton, shuffleBtn, menuBtn),
container.NewHBox(util.NewHSpace(2), a.toggleFavButton),
@@ -262,6 +277,9 @@ func (a *AlbumPageHeader) Update(album *mediaprovider.AlbumWithTracks, im *backe
a.albumID = album.ID
a.coverID = album.CoverArtID
a.titleLabel.Segments[0].(*widget.TextSegment).Text = album.Name
a.releaseTypeLabel.Segments[0].(*widget.TextSegment).Text = util.DisplayReleaseType(album.ReleaseTypes)
a.releaseTypeLabel.Refresh() // needed so MinSize returns correct width below
a.artistLabelSpace.Width = a.releaseTypeLabel.MinSize().Width - 16
a.artistLabel.BuildSegments(album.ArtistNames, album.ArtistIDs)
a.genreLabel.BuildSegments(album.Genres, album.Genres)
a.miscLabel.SetText(formatMiscLabelStr(album))
+1 -1
View File
@@ -74,7 +74,7 @@ func NewQuickSearch(mp mediaprovider.MediaProvider, im util.ImageFetcher) *Quick
)
dismissBtn := widget.NewButton("Close", q.onDismiss)
title := widget.NewRichText(&widget.TextSegment{Text: "Quick Search", Style: boldStyle})
title := widget.NewRichText(&widget.TextSegment{Text: "Quick Search", Style: util.BoldRichTextStyle})
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
q.content = container.NewBorder(
container.NewVBox(title, se),
+4 -6
View File
@@ -26,8 +26,6 @@ import (
"fyne.io/fyne/v2/widget"
)
var boldStyle = widget.RichTextStyle{TextStyle: fyne.TextStyle{Bold: true}}
type SettingsDialog struct {
widget.BaseWidget
@@ -236,7 +234,7 @@ func (s *SettingsDialog) createGeneralTab() *container.TabItem {
container.NewHBox(systemTrayEnable, closeToTray),
s.newSectionSeparator(),
widget.NewRichText(&widget.TextSegment{Text: "Scrobbling", Style: boldStyle}),
widget.NewRichText(&widget.TextSegment{Text: "Scrobbling", Style: util.BoldRichTextStyle}),
scrobbleEnabled,
container.NewHBox(
widget.NewLabel("Scrobble when"),
@@ -339,7 +337,7 @@ func (s *SettingsDialog) createPlaybackTab() *container.TabItem {
)),
s.newSectionSeparator(),
widget.NewRichText(&widget.TextSegment{Text: "ReplayGain", Style: boldStyle}),
widget.NewRichText(&widget.TextSegment{Text: "ReplayGain", Style: util.BoldRichTextStyle}),
container.New(layout.NewFormLayout(),
widget.NewLabel("ReplayGain mode"), container.NewGridWithColumns(2, replayGainSelect),
widget.NewLabel("ReplayGain preamp"), container.NewHBox(preampGain, widget.NewLabel("dB")),
@@ -423,10 +421,10 @@ func (s *SettingsDialog) createExperimentalTab(window fyne.Window) *container.Ta
return container.NewTabItem("Experimental", container.NewVBox(
warningLabel,
s.newSectionSeparator(),
widget.NewRichText(&widget.TextSegment{Text: "UI Scaling", Style: boldStyle}),
widget.NewRichText(&widget.TextSegment{Text: "UI Scaling", Style: util.BoldRichTextStyle}),
uiScaleRadio,
s.newSectionSeparator(),
widget.NewRichText(&widget.TextSegment{Text: "Application Font", Style: boldStyle}),
widget.NewRichText(&widget.TextSegment{Text: "Application Font", Style: util.BoldRichTextStyle}),
container.New(layout.NewFormLayout(),
widget.NewLabel("Normal font"), container.NewBorder(nil, nil, nil, normalFontBrowse, normalFontEntry),
widget.NewLabel("Bold font"), container.NewBorder(nil, nil, nil, boldFontBrowse, boldFontEntry),
+49
View File
@@ -14,11 +14,14 @@ import (
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/ui/layouts"
myTheme "github.com/dweymouth/supersonic/ui/theme"
"golang.org/x/net/html"
)
var BoldRichTextStyle = widget.RichTextStyle{TextStyle: fyne.TextStyle{Bold: true}, Inline: true}
func SecondsToTimeString(s float64) string {
if s < 0 {
s = 0
@@ -109,6 +112,52 @@ func PlaintextFromHTMLString(s string) string {
return text
}
func DisplayReleaseType(releaseTypes mediaprovider.ReleaseTypes) string {
baseType := "Album"
switch {
case releaseTypes&mediaprovider.ReleaseTypeAudiobook > 0:
baseType = "Audiobook"
case releaseTypes&mediaprovider.ReleaseTypeAudioDrama > 0:
baseType = "Audio Drama"
case releaseTypes&mediaprovider.ReleaseTypeBroadcast > 0:
baseType = "Broadcast"
case releaseTypes&mediaprovider.ReleaseTypeDJMix > 0:
baseType = "DJ-Mix"
case releaseTypes&mediaprovider.ReleaseTypeEP > 0:
baseType = "EP"
case releaseTypes&mediaprovider.ReleaseTypeFieldRecording > 0:
baseType = "Field Recording"
case releaseTypes&mediaprovider.ReleaseTypeInterview > 0:
baseType = "Interview"
case releaseTypes&mediaprovider.ReleaseTypeMixtape > 0:
baseType = "Mixtape"
case releaseTypes&mediaprovider.ReleaseTypeSingle > 0:
baseType = "Single"
case releaseTypes&mediaprovider.ReleaseTypeSoundtrack > 0:
baseType = "Soundtrack"
}
var modifiers []string
if releaseTypes&mediaprovider.ReleaseTypeLive > 0 {
modifiers = append(modifiers, "Live")
}
if releaseTypes&mediaprovider.ReleaseTypeDemo > 0 {
modifiers = append(modifiers, "Demo")
}
if releaseTypes&mediaprovider.ReleaseTypeRemix > 0 {
modifiers = append(modifiers, "Remix")
}
if releaseTypes&mediaprovider.ReleaseTypeSpokenWord > 0 {
modifiers = append(modifiers, "Spoken Word")
}
if releaseTypes&mediaprovider.ReleaseTypeCompilation > 0 {
modifiers = append(modifiers, "Compilation")
}
modifiers = append(modifiers, baseType)
return strings.Join(modifiers, " ")
}
func NewRatingSubmenu(onSetRating func(int)) *fyne.MenuItem {
newRatingMenuItem := func(rating int) *fyne.MenuItem {
label := "(none)"