package e2e
import (
"github.com/navidrome/navidrome/server/subsonic/responses"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Lyrics endpoints", func() {
BeforeEach(func() {
setupTestDB()
})
songID := func(title string) string {
resp := doReq("search3", "query", title, "songCount", "1", "artistCount", "0", "albumCount", "0")
Expect(resp.Status).To(Equal("ok"))
Expect(resp.SearchResult3).ToNot(BeNil())
Expect(resp.SearchResult3.Song).ToNot(BeEmpty(), "expected to find song %q", title)
return resp.SearchResult3.Song[0].Id
}
firstLyric := func(list *responses.LyricsList) responses.StructuredLyric {
Expect(list).ToNot(BeNil())
Expect(list.StructuredLyrics).ToNot(BeEmpty())
return list.StructuredLyrics[0]
}
Describe("getLyricsBySongId v1 (line-level, not enhanced)", func() {
DescribeTable("returns line-level lyrics without enhanced fields",
func(title string, wantSynced bool, wantLang string) {
resp := doReq("getLyricsBySongId", "id", songID(title))
Expect(resp.Status).To(Equal("ok"))
got := firstLyric(resp.LyricsList)
Expect(got.Synced).To(Equal(wantSynced))
Expect(got.Lang).To(Equal(wantLang))
Expect(got.Line).ToNot(BeEmpty())
Expect(got.Line[0].Value).To(Equal(firstFixtureLine))
Expect(got.CueLine).To(BeEmpty())
Expect(got.Kind).To(BeEmpty())
Expect(got.Agents).To(BeEmpty())
},
Entry("embedded enhanced LRC (word-level)", "Embedded Enhanced LRC", true, "eng"),
Entry("embedded plain text", "Embedded Plain", false, "xxx"),
Entry("embedded TTML", "Embedded TTML", true, "eng"),
Entry("LRC sidecar", "Sidecar LRC", true, "eng"),
Entry("SRT sidecar", "Sidecar SRT", true, "xxx"),
Entry("YAML sidecar (word-level)", "Sidecar YAML", true, "eng"),
)
})
Describe("getLyricsBySongId v2 (enhanced)", func() {
DescribeTable("returns enhanced lyrics, with cueLine only for word-level sources",
func(title string, wantCueLine bool) {
resp := doReq("getLyricsBySongId", "id", songID(title), "enhanced", "true")
Expect(resp.Status).To(Equal("ok"))
got := firstLyric(resp.LyricsList)
Expect(got.Kind).To(Equal("main"))
if wantCueLine {
Expect(got.CueLine).ToNot(BeEmpty())
Expect(got.CueLine[0].Cue).To(HaveLen(5))
Expect(got.CueLine[0].Cue[0].Value).To(Equal("Should "))
} else {
Expect(got.CueLine).To(BeEmpty())
Expect(got.Line).ToNot(BeEmpty())
}
},
Entry("embedded enhanced LRC (word-level)", "Embedded Enhanced LRC", true),
Entry("embedded TTML (word-level spans)", "Embedded TTML", true),
Entry("YAML sidecar (word-level)", "Sidecar YAML", true),
Entry("embedded plain text (no timing)", "Embedded Plain", false),
Entry("LRC sidecar (line-level)", "Sidecar LRC", false),
Entry("SRT sidecar (line-level)", "Sidecar SRT", false),
)
})
Describe("getLyrics (legacy artist/title)", func() {
DescribeTable("returns the main lyric as plain text across formats and sources",
func(title string) {
resp := doReq("getLyrics", "artist", "Lyric Tester", "title", title)
Expect(resp.Status).To(Equal("ok"))
Expect(resp.Lyrics).ToNot(BeNil())
Expect(resp.Lyrics.Artist).To(Equal("Lyric Tester"))
Expect(resp.Lyrics.Title).To(Equal(title))
Expect(resp.Lyrics.Value).To(ContainSubstring(firstFixtureLine))
Expect(resp.Lyrics.Value).ToNot(ContainSubstring("["))
Expect(resp.Lyrics.Value).ToNot(ContainSubstring("-->"))
Expect(resp.Lyrics.Value).ToNot(ContainSubstring("<"))
},
Entry("embedded enhanced LRC", "Embedded Enhanced LRC"),
Entry("embedded plain text", "Embedded Plain"),
Entry("embedded TTML", "Embedded TTML"),
Entry("LRC sidecar", "Sidecar LRC"),
Entry("SRT sidecar", "Sidecar SRT"),
Entry("YAML sidecar", "Sidecar YAML"),
)
})
})