Add OS Lyrics extension (#2656)
* draft commit * time to fight pipeline * round 2 changes * remove unnecessary line * fight taglib. again * make taglib work again??? * add id3 tags * taglib 1.12 vs 1.13 * use int instead for windows * store as json now * add migration, more tests * support repeated line, multiline * fix ms and support .m, .mm, .mmm * address some concerns, make cpp a bit safer * separate responses from model * remove [:] * Add trace log * Try to unblock pipeline * Fix merge errors * Fix SIGSEGV error (proper handling of empty frames) * Add fallback artist/title to structured lyrics * Rename conflicting named vars * Fix tests * Do we still need ffmpeg in the pipeline? * Revert "Do we still need ffmpeg in the pipeline?" Yes we do. This reverts commit 87df7f6df79bccee83f48c4b7a8118a7636a5e66. * Does this passes now, with a newer ffmpeg version? * Revert "Does this passes now, with a newer ffmpeg version?" No, it does not :( This reverts commit 372eb4b0ae05d9ffe98078e9bc4e56a9b2921f32. * My OCD made me do it :P --------- Co-authored-by: Deluan Quintão <deluan@navidrome.org>
This commit is contained in:
@@ -3,6 +3,7 @@ package subsonic
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/server/subsonic/responses"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -72,12 +74,18 @@ var _ = Describe("MediaRetrievalController", func() {
|
||||
Describe("GetLyrics", func() {
|
||||
It("should return data for given artist & title", func() {
|
||||
r := newGetRequest("artist=Rick+Astley", "title=Never+Gonna+Give+You+Up")
|
||||
lyrics, _ := model.ToLyrics("eng", "[00:18.80]We're no strangers to love\n[00:22.80]You know the rules and so do I")
|
||||
lyricsJson, err := json.Marshal(model.LyricList{
|
||||
*lyrics,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
mockRepo.SetData(model.MediaFiles{
|
||||
{
|
||||
ID: "1",
|
||||
Artist: "Rick Astley",
|
||||
Title: "Never Gonna Give You Up",
|
||||
Lyrics: "[00:18.80]We're no strangers to love\n[00:22.80]You know the rules and so do I",
|
||||
Lyrics: string(lyricsJson),
|
||||
},
|
||||
})
|
||||
response, err := router.GetLyrics(r)
|
||||
@@ -87,7 +95,7 @@ var _ = Describe("MediaRetrievalController", func() {
|
||||
Expect(err).To(BeNil())
|
||||
Expect(response.Lyrics.Artist).To(Equal("Rick Astley"))
|
||||
Expect(response.Lyrics.Title).To(Equal("Never Gonna Give You Up"))
|
||||
Expect(response.Lyrics.Value).To(Equal("We're no strangers to love\nYou know the rules and so do I"))
|
||||
Expect(response.Lyrics.Value).To(Equal("We're no strangers to love\nYou know the rules and so do I\n"))
|
||||
})
|
||||
It("should return empty subsonic response if the record corresponding to the given artist & title is not found", func() {
|
||||
r := newGetRequest("artist=Dheeraj", "title=Rinkiya+Ke+Papa")
|
||||
@@ -100,7 +108,143 @@ var _ = Describe("MediaRetrievalController", func() {
|
||||
Expect(response.Lyrics.Artist).To(Equal(""))
|
||||
Expect(response.Lyrics.Title).To(Equal(""))
|
||||
Expect(response.Lyrics.Value).To(Equal(""))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getLyricsBySongId", 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"
|
||||
const metadata = "[ar:Rick Astley]\n[ti:That one song]\n[offset:-100]"
|
||||
var times = []int64{18800, 22801}
|
||||
|
||||
compareResponses := func(actual *responses.LyricsList, expected responses.LyricsList) {
|
||||
Expect(actual).ToNot(BeNil())
|
||||
Expect(actual.StructuredLyrics).To(HaveLen(len(expected.StructuredLyrics)))
|
||||
for i, realLyric := range actual.StructuredLyrics {
|
||||
expectedLyric := expected.StructuredLyrics[i]
|
||||
|
||||
Expect(realLyric.DisplayArtist).To(Equal(expectedLyric.DisplayArtist))
|
||||
Expect(realLyric.DisplayTitle).To(Equal(expectedLyric.DisplayTitle))
|
||||
Expect(realLyric.Lang).To(Equal(expectedLyric.Lang))
|
||||
Expect(realLyric.Synced).To(Equal(expectedLyric.Synced))
|
||||
|
||||
if expectedLyric.Offset == nil {
|
||||
Expect(realLyric.Offset).To(BeNil())
|
||||
} else {
|
||||
Expect(*realLyric.Offset).To(Equal(*expectedLyric.Offset))
|
||||
}
|
||||
|
||||
Expect(realLyric.Line).To(HaveLen(len(expectedLyric.Line)))
|
||||
for j, realLine := range realLyric.Line {
|
||||
expectedLine := expectedLyric.Line[j]
|
||||
Expect(realLine.Value).To(Equal(expectedLine.Value))
|
||||
|
||||
if expectedLine.Start == nil {
|
||||
Expect(realLine.Start).To(BeNil())
|
||||
} else {
|
||||
Expect(*realLine.Start).To(Equal(*expectedLine.Start))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It("should return mixed lyrics", func() {
|
||||
r := newGetRequest("id=1")
|
||||
synced, _ := model.ToLyrics("eng", syncedLyrics)
|
||||
unsynced, _ := model.ToLyrics("xxx", unsyncedLyrics)
|
||||
lyricsJson, err := json.Marshal(model.LyricList{
|
||||
*synced, *unsynced,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
mockRepo.SetData(model.MediaFiles{
|
||||
{
|
||||
ID: "1",
|
||||
Artist: "Rick Astley",
|
||||
Title: "Never Gonna Give You Up",
|
||||
Lyrics: string(lyricsJson),
|
||||
},
|
||||
})
|
||||
|
||||
response, err := router.GetLyricsBySongId(r)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
compareResponses(response.LyricsList, responses.LyricsList{
|
||||
StructuredLyrics: responses.StructuredLyrics{
|
||||
{
|
||||
Lang: "eng",
|
||||
DisplayArtist: "Rick Astley",
|
||||
DisplayTitle: "Never Gonna Give You Up",
|
||||
Synced: true,
|
||||
Line: []responses.Line{
|
||||
{
|
||||
Start: ×[0],
|
||||
Value: "We're no strangers to love",
|
||||
},
|
||||
{
|
||||
Start: ×[1],
|
||||
Value: "You know the rules and so do I",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Lang: "xxx",
|
||||
DisplayArtist: "Rick Astley",
|
||||
DisplayTitle: "Never Gonna Give You Up",
|
||||
Synced: false,
|
||||
Line: []responses.Line{
|
||||
{
|
||||
Value: "We're no strangers to love",
|
||||
},
|
||||
{
|
||||
Value: "You know the rules and so do I",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
It("should parse lrc metadata", func() {
|
||||
r := newGetRequest("id=1")
|
||||
synced, _ := model.ToLyrics("eng", metadata+"\n"+syncedLyrics)
|
||||
lyricsJson, err := json.Marshal(model.LyricList{
|
||||
*synced,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
mockRepo.SetData(model.MediaFiles{
|
||||
{
|
||||
ID: "1",
|
||||
Artist: "Rick Astley",
|
||||
Title: "Never Gonna Give You Up",
|
||||
Lyrics: string(lyricsJson),
|
||||
},
|
||||
})
|
||||
|
||||
response, err := router.GetLyricsBySongId(r)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
offset := int64(-100)
|
||||
compareResponses(response.LyricsList, responses.LyricsList{
|
||||
StructuredLyrics: responses.StructuredLyrics{
|
||||
{
|
||||
DisplayArtist: "Rick Astley",
|
||||
DisplayTitle: "That one song",
|
||||
Lang: "eng",
|
||||
Synced: true,
|
||||
Line: []responses.Line{
|
||||
{
|
||||
Start: ×[0],
|
||||
Value: "We're no strangers to love",
|
||||
},
|
||||
{
|
||||
Start: ×[1],
|
||||
Value: "You know the rules and so do I",
|
||||
},
|
||||
},
|
||||
Offset: &offset,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -122,26 +266,6 @@ func (c *fakeArtwork) GetOrPlaceholder(_ context.Context, id string, size int) (
|
||||
return io.NopCloser(bytes.NewReader([]byte(c.data))), time.Time{}, nil
|
||||
}
|
||||
|
||||
var _ = Describe("isSynced", func() {
|
||||
It("returns false if lyrics contain no timestamps", func() {
|
||||
Expect(isSynced("Just in case my car goes off the highway")).To(Equal(false))
|
||||
Expect(isSynced("[02.50] Just in case my car goes off the highway")).To(Equal(false))
|
||||
})
|
||||
It("returns false if lyrics is an empty string", func() {
|
||||
Expect(isSynced("")).To(Equal(false))
|
||||
})
|
||||
It("returns true if lyrics contain timestamps", func() {
|
||||
Expect(isSynced(`NF Real Music
|
||||
[00:00] First line
|
||||
[00:00.85] JUST LIKE YOU
|
||||
[00:00.85] Just in case my car goes off the highway`)).To(Equal(true))
|
||||
Expect(isSynced("[04:02:50.85] Never gonna give you up")).To(Equal(true))
|
||||
Expect(isSynced("[02:50.85] Never gonna give you up")).To(Equal(true))
|
||||
Expect(isSynced("[02:50] Never gonna give you up")).To(Equal(true))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
type mockedMediaFile struct {
|
||||
model.MediaFileRepository
|
||||
data model.MediaFiles
|
||||
@@ -154,3 +278,12 @@ func (m *mockedMediaFile) SetData(mfs model.MediaFiles) {
|
||||
func (m *mockedMediaFile) GetAll(...model.QueryOptions) (model.MediaFiles, error) {
|
||||
return m.data, nil
|
||||
}
|
||||
|
||||
func (m *mockedMediaFile) Get(id string) (*model.MediaFile, error) {
|
||||
for _, mf := range m.data {
|
||||
if mf.ID == id {
|
||||
return &mf, nil
|
||||
}
|
||||
}
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user