package lyrics import ( "context" "encoding/json" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/utils/gg" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("sources", func() { ctx := context.Background() Describe("fromEmbedded", func() { It("should return nothing for a media file with no lyrics", func() { mf := model.MediaFile{} lyrics, err := fromEmbedded(ctx, &mf) Expect(err).To(BeNil()) Expect(lyrics).To(HaveLen(0)) }) It("should return lyrics for a media file with well-formatted lyrics", func() { const syncedLyrics = "[00:18.80]We're no strangers to love\n[00:22.801]You know the rules and so do I" const unsyncedLyrics = "We're no strangers to love\nYou know the rules and so do I" synced, _ := model.ToLyrics("eng", syncedLyrics) unsynced, _ := model.ToLyrics("xxx", unsyncedLyrics) expectedList := model.LyricList{*synced, *unsynced} lyricsJson, err := json.Marshal(expectedList) Expect(err).ToNot(HaveOccurred()) mf := model.MediaFile{ Lyrics: string(lyricsJson), } lyrics, err := fromEmbedded(ctx, &mf) Expect(err).To(BeNil()) Expect(lyrics).ToNot(BeNil()) Expect(lyrics).To(Equal(expectedList)) }) It("should return an error if somehow the JSON is bad", func() { mf := model.MediaFile{Lyrics: "["} lyrics, err := fromEmbedded(ctx, &mf) Expect(lyrics).To(HaveLen(0)) Expect(err).ToNot(BeNil()) }) }) Describe("fromExternalFile", func() { It("should return nil for lyrics that don't exist", func() { mf := model.MediaFile{Path: "tests/fixtures/01 Invisible (RED) Edit Version.mp3"} lyrics, err := fromExternalFile(ctx, &mf, ".lrc") Expect(err).To(BeNil()) Expect(lyrics).To(HaveLen(0)) }) It("should return synchronized lyrics from a file", func() { mf := model.MediaFile{Path: "tests/fixtures/test.mp3"} lyrics, err := fromExternalFile(ctx, &mf, ".lrc") Expect(err).To(BeNil()) Expect(lyrics).To(Equal(model.LyricList{ model.Lyrics{ DisplayArtist: "Rick Astley", DisplayTitle: "That one song", Lang: "eng", Line: []model.Line{ { Start: gg.P(int64(18800)), Value: "We're no strangers to love", }, { Start: gg.P(int64(22801)), Value: "You know the rules and so do I", }, }, Offset: gg.P(int64(-100)), Synced: true, }, })) }) It("should return unsynchronized lyrics from a file", func() { mf := model.MediaFile{Path: "tests/fixtures/test.mp3"} lyrics, err := fromExternalFile(ctx, &mf, ".txt") Expect(err).To(BeNil()) Expect(lyrics).To(Equal(model.LyricList{ model.Lyrics{ Lang: "xxx", Line: []model.Line{ { Value: "We're no strangers to love", }, { Value: "You know the rules and so do I", }, }, Synced: false, }, })) }) It("should handle LRC files with UTF-8 BOM marker (issue #4631)", func() { // The function looks for , so we need to pass // a MediaFile with .mp3 path and look for .lrc suffix mf := model.MediaFile{Path: "tests/fixtures/bom-test.mp3"} lyrics, err := fromExternalFile(ctx, &mf, ".lrc") Expect(err).To(BeNil()) Expect(lyrics).ToNot(BeNil()) Expect(lyrics).To(HaveLen(1)) // The critical assertion: even with BOM, synced should be true Expect(lyrics[0].Synced).To(BeTrue(), "Lyrics with BOM marker should be recognized as synced") Expect(lyrics[0].Line).To(HaveLen(1)) Expect(lyrics[0].Line[0].Start).To(Equal(gg.P(int64(0)))) Expect(lyrics[0].Line[0].Value).To(ContainSubstring("作曲")) }) It("should handle UTF-16 LE encoded LRC files", func() { mf := model.MediaFile{Path: "tests/fixtures/bom-utf16-test.mp3"} lyrics, err := fromExternalFile(ctx, &mf, ".lrc") Expect(err).To(BeNil()) Expect(lyrics).ToNot(BeNil()) Expect(lyrics).To(HaveLen(1)) // UTF-16 should be properly converted to UTF-8 Expect(lyrics[0].Synced).To(BeTrue(), "UTF-16 encoded lyrics should be recognized as synced") Expect(lyrics[0].Line).To(HaveLen(2)) Expect(lyrics[0].Line[0].Start).To(Equal(gg.P(int64(18800)))) Expect(lyrics[0].Line[0].Value).To(Equal("We're no strangers to love")) Expect(lyrics[0].Line[1].Start).To(Equal(gg.P(int64(22801)))) Expect(lyrics[0].Line[1].Value).To(Equal("You know the rules and so do I")) }) }) })