support fetching artist bio from filesystem

This commit is contained in:
Kendall Garner 2024-03-09 19:58:43 -08:00
parent 1490e9c1e6
commit 4621e9106f
No known key found for this signature in database
GPG Key ID: 18D2767419676C87
7 changed files with 175 additions and 43 deletions

View File

@ -229,7 +229,7 @@ func disableExternalServices() {
Server.LastFM.Enabled = false
Server.Spotify.ID = ""
Server.ListenBrainz.Enabled = false
Server.Agents = ""
Server.Agents = "filesystem"
if Server.UILoginBackgroundURL == consts.DefaultUILoginBackgroundURL {
Server.UILoginBackgroundURL = consts.DefaultUILoginBackgroundURLOffline
}
@ -336,7 +336,7 @@ func init() {
viper.SetDefault("scanner.genreseparators", ";/,")
viper.SetDefault("scanner.groupalbumreleases", false)
viper.SetDefault("agents", "lastfm,spotify,local,lrclib")
viper.SetDefault("agents", "filesystem,lastfm,spotify,lrclib")
viper.SetDefault("lastfm.enabled", true)
viper.SetDefault("lastfm.language", "en")
viper.SetDefault("lastfm.apikey", "")

View File

@ -0,0 +1,96 @@
package filesystem
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils"
)
const filesystemAgentName = "filesystem"
var (
supportedExtensions = []string{"lrc", "txt"}
)
type filesystemAgent struct {
ds model.DataStore
}
func filesystemConstructor(ds model.DataStore) *filesystemAgent {
return &filesystemAgent{
ds: ds,
}
}
func (f *filesystemAgent) AgentName() string {
return filesystemAgentName
}
func (f *filesystemAgent) GetArtistBiography(ctx context.Context, id, name, mbid string) (string, error) {
_, err := f.ds.Artist(ctx).Get(id)
if err != nil {
return "", err
}
als, err := f.ds.Album(ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"album_artist_id": id}})
if err != nil {
return "", err
}
var paths []string
for _, al := range als {
paths = append(paths, strings.Split(al.Paths, consts.Zwsp)...)
}
artistFolder := utils.LongestCommonPrefix(paths)
if !strings.HasSuffix(artistFolder, string(filepath.Separator)) {
artistFolder, _ = filepath.Split(artistFolder)
}
artistBioPath := filepath.Join(artistFolder, "artist.txt")
contents, err := os.ReadFile(artistBioPath)
if err != nil {
return "", agents.ErrNotFound
}
return string(contents), nil
}
func (f *filesystemAgent) GetSongLyrics(ctx context.Context, mf *model.MediaFile) (model.LyricList, error) {
lyrics := model.LyricList{}
extension := filepath.Ext(mf.Path)
basePath := mf.Path[0 : len(mf.Path)-len(extension)]
for _, ext := range supportedExtensions {
lrcPath := fmt.Sprintf("%s.%s", basePath, ext)
contents, err := os.ReadFile(lrcPath)
if err != nil {
continue
}
lyric, err := model.ToLyrics("xxx", string(contents))
if err != nil {
return nil, err
}
lyrics = append(lyrics, *lyric)
}
return lyrics, nil
}
func init() {
conf.AddHook(func() {
agents.Register(filesystemAgentName, func(ds model.DataStore) agents.Interface {
return filesystemConstructor(ds)
})
})
}
var _ agents.ArtistBiographyRetriever = (*filesystemAgent)(nil)
var _ agents.LyricsRetriever = (*filesystemAgent)(nil)

View File

@ -1,8 +1,9 @@
package agents
package filesystem
import (
"context"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/tests"
. "github.com/navidrome/navidrome/utils/gg"
@ -13,23 +14,73 @@ import (
var _ = Describe("localAgent", func() {
var ds model.DataStore
var ctx context.Context
var agent Interface
var agent *filesystemAgent
BeforeEach(func() {
ds = &tests.MockDataStore{}
ctx = context.Background()
agent = localsConstructor(ds)
agent = filesystemConstructor(ds)
})
Describe("GetArtistBiography", func() {
BeforeEach(func() {
ds.Artist(ctx).(*tests.MockArtistRepo).SetData(model.Artists{
model.Artist{ID: "ar-1234",
Name: "artist"},
})
})
It("should fetch artist biography", func() {
ds.Album(ctx).(*tests.MockAlbumRepo).SetData(model.Albums{
model.Album{ID: "al-1234",
AlbumArtistID: "ar-1234",
Name: "album",
Paths: "tests/fixtures/artist/an-album",
},
})
bio, err := agent.GetArtistBiography(ctx, "ar-1234", "album", "")
Expect(err).ToNot(HaveOccurred())
Expect(bio).To(Equal("This is an artist biography"))
})
It("should fetch artist biography with slash", func() {
ds.Album(ctx).(*tests.MockAlbumRepo).SetData(model.Albums{
model.Album{ID: "al-1234",
AlbumArtistID: "ar-1234",
Name: "album",
Paths: "tests/fixtures/artist/",
},
})
bio, err := agent.GetArtistBiography(ctx, "ar-1234", "album", "")
Expect(err).ToNot(HaveOccurred())
Expect(bio).To(Equal("This is an artist biography"))
})
It("should error when file doesn't exist", func() {
ds.Album(ctx).(*tests.MockAlbumRepo).SetData(model.Albums{
model.Album{ID: "al-1234",
AlbumArtistID: "ar-1234",
Name: "album",
Paths: "tests/fixtures/fake-artist/fake-album",
},
})
bio, err := agent.GetArtistBiography(ctx, "ar-1234", "album", "")
Expect(err).To(Equal(agents.ErrNotFound))
Expect(bio).To(Equal(""))
})
})
Describe("GetSongLyrics", func() {
It("should parse LRC file", func() {
lyricFetcher, _ := agent.(LyricsRetriever)
mf := model.MediaFile{
Path: "tests/fixtures/01 Invisible (RED) Edit Version.mp3",
}
lyrics, err := lyricFetcher.GetSongLyrics(ctx, &mf)
lyrics, err := agent.GetSongLyrics(ctx, &mf)
Expect(err).ToNot(HaveOccurred())
Expect(lyrics).To(Equal(model.LyricList{
{
@ -48,13 +99,11 @@ var _ = Describe("localAgent", func() {
})
It("should parse both LRC and TXT", func() {
lyricFetcher, _ := agent.(LyricsRetriever)
mf := model.MediaFile{
Path: "tests/fixtures/test.wav",
}
lyrics, err := lyricFetcher.GetSongLyrics(ctx, &mf)
lyrics, err := agent.GetSongLyrics(ctx, &mf)
Expect(err).ToNot(HaveOccurred())
Expect(lyrics).To(Equal(model.LyricList{
{

View File

@ -0,0 +1,17 @@
package filesystem
import (
"testing"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestFilesystem(t *testing.T) {
tests.Init(t, false)
log.SetLevel(log.LevelFatal)
RegisterFailHandler(Fail)
RunSpecs(t, "Spotify Test Suite")
}

View File

@ -2,9 +2,6 @@ package agents
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/model"
@ -12,10 +9,6 @@ import (
const LocalAgentName = "local"
var (
supportedExtensions = []string{"lrc", "txt"}
)
type localAgent struct {
ds model.DataStore
}
@ -54,33 +47,8 @@ func (p *localAgent) GetArtistTopSongs(ctx context.Context, id, artistName, mbid
return result, nil
}
func (p *localAgent) GetSongLyrics(ctx context.Context, mf *model.MediaFile) (model.LyricList, error) {
lyrics := model.LyricList{}
extension := filepath.Ext(mf.Path)
basePath := mf.Path[0 : len(mf.Path)-len(extension)]
for _, ext := range supportedExtensions {
lrcPath := fmt.Sprintf("%s.%s", basePath, ext)
contents, err := os.ReadFile(lrcPath)
if err != nil {
continue
}
lyric, err := model.ToLyrics("xxx", string(contents))
if err != nil {
return nil, err
}
lyrics = append(lyrics, *lyric)
}
return lyrics, nil
}
func init() {
Register(LocalAgentName, localsConstructor)
}
var _ ArtistTopSongsRetriever = (*localAgent)(nil)
var _ LyricsRetriever = (*localAgent)(nil)

View File

@ -13,6 +13,7 @@ import (
"github.com/deluan/sanitize"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/core/agents"
_ "github.com/navidrome/navidrome/core/agents/filesystem"
_ "github.com/navidrome/navidrome/core/agents/lastfm"
_ "github.com/navidrome/navidrome/core/agents/listenbrainz"
_ "github.com/navidrome/navidrome/core/agents/lrclib"

1
tests/fixtures/artist/artist.txt vendored Normal file
View File

@ -0,0 +1 @@
This is an artist biography